Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>: A = 1; * N_7: print(A); * * At N_3, reads of A in {N_4, N_5} are said to be upward exposed. */ static final class ReachingUses implements LatticeElement { final Multimap<Var, Node> mayUseMap; public ReachingUses() { mayUseMap = HashMultimap.create(); } /** * Copy constructor. * * @param other The constructed object is a replicated copy of this element. */ public ReachingUses(ReachingUses other) { mayUseMap = HashMultimap.create(other.mayUseMap); } @Override public boolean equals(Object other) { return (other instanceof ReachingUses) && ((ReachingUses) other).mayUseMap.equals(this.mayUseMap); } @Override public int hashCode() { return mayUseMap.hashCode(); } } /** * The join is a simple union because of the "may be" nature of the analysis. * * Consider: A = 1; if (x) { A = 2 }; alert(A); * * The read of A "may be" exposed to A = 1 in the beginning. */ private static class ReachingUsesJoinOp implements JoinOp<ReachingUses> { @Override public ReachingUses apply(List<ReachingUses> from) { ReachingUses result = new ReachingUses(); for (ReachingUses uses : from) { result.mayUseMap.putAll(uses.mayUseMap); } return result; } } @Override boolean isForward() { return false; } @Override case Token.DO: case Token.IF: computeMayUse( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); return; case Token.FOR: if (!NodeUtil.isForIn(n)) { computeMayUse( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); } else { // for(x in y) {...} Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (lhs.isVar()) { lhs = lhs.getLastChild(); // for(var x in y) {...} } if (lhs.isName() && !conditional) { removeFromUseIfLocal(lhs.getString(), output); } computeMayUse(rhs, cfgNode, output, conditional); } return; case Token.AND: case Token.OR: computeMayUse(n.getLastChild(), cfgNode, output, true); computeMayUse(n.getFirstChild(), cfgNode, output, conditional); return; case Token.HOOK: computeMayUse(n.getLastChild(), cfgNode, output, true); computeMayUse(n.getFirstChild().getNext(), cfgNode, output, true); computeMayUse(n.getFirstChild(), cfgNode, output, conditional); return; case Token.VAR: Node varName = n.getFirstChild(); Preconditions.checkState(n.hasChildren(), "AST should be normalized"); if (varName.hasChildren()) { computeMayUse(varName.getFirstChild(), cfgNode, output, conditional); if (!conditional) { removeFromUseIfLocal(varName.getString(), output); } } return; default: if

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> (NodeUtil.isAssignmentOp(n) && n.getFirstChild().isName()) { Node name = n.getFirstChild(); if (!conditional) { removeFromUseIfLocal(name.getString(), output); } // In case of a += "Hello". There is a read of a. if (!n.isAssign()) { addToUseIfLocal(name.getString(), cfgNode, output); } computeMayUse(name.getNext(), cfgNode, output, conditional); } else { /* * We want to traverse in reverse order because we want the LAST * definition in the sub-tree.... * But we have no better way to traverse in reverse other :'( */ for (Node c = n.getLastChild(); c != null; c = n.getChildBefore(c)) { computeMayUse(c, cfgNode, output, conditional); } } } } /** * Sets the variable for the given name to the node value in the upward * exposed lattice. Do nothing if the variable name is one of the escaped * variable. */ private void addToUseIfLocal(String name, Node node, ReachingUses use) { Var var = jsScope.getVar(name); if (var == null || var.scope != jsScope) { return; } if (!escaped.contains(var)) { use.mayUseMap.put(var, node); } } /** * Removes the variable for the given name from the node value in the upward * exposed lattice. Do nothing if the variable name is one of the escaped * variable. */ private void removeFromUseIfLocal(String name, ReachingUses use) { Var var = jsScope.getVar(name); if (var == null || var.scope != jsScope) { return; } if (!escaped.contains(var)) { use.mayUseMap.removeAll(var); } } /** * Gets a list of nodes that may be using the value assigned to {@code name} * in {@code defNode}. {@code defNode} must be one of the control flow graph * nodes. * * @param name name of the variable. It can only be names of local variable * that are not function parameters, escaped variables or variables * declared in catch. * @param defNode The list of upward exposed use for the variable. */ Collection<Node> getUses(String name, Node defNode) { GraphNode<Node, Branch> n = getCfg().getNode(defNode); Preconditions.checkNotNull(n); FlowState<ReachingUses> state = n.getAnnotation(); return state.getOut().mayUseMap.get(jsScope.getVar(name)); } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2012 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.javascript.jscomp.DefaultPassConfig.HotSwapPassFactory; import com.google.javascript.jscomp.GlobalVarReferenceMap.GlobalVarRefCleanupPass; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import java.util.List; /** * Provides passes that should be run before hot-swap/incremental builds. * * @author tylerg@google.com (Tyler Goodwin) */ class CleanupPasses extends PassConfig { private State state; public CleanupPasses(CompilerOptions options) { super(options); } @Override protected List<PassFactory> getChecks() { List<PassFactory> checks = Lists.newArrayList(); checks.add(fieldCleanupPassFactory); checks.add(scopeCleanupPassFactory); checks.add(globalVarRefCleanupPassFactory); return checks; } @Override protected State getIntermediateState() { return state; } @Override protected List<PassFactory> getOptimizations() { return ImmutableList.of(); } @Override protected void setIntermediateState(State state) { this.state = state; } final PassFactory fieldCleanupPassFactory = new HotSwapPassFactory("FieldCleaupPassFactory", false) { @Override protected HotSwapCompilerPass createInternal( AbstractCompiler compiler) { return new FieldCleanupPass(compiler); } }; final PassFactory scopeCleanupPassFactory = new HotSwapPassFactory("ScopeCleanupPassFactory", false) { @Override protected HotSwapCompilerPass createInternal( AbstractCompiler compiler) { return new MemoizedScopeCleanupPass(compiler); } }; final PassFactory globalVarRefCleanupPassFactory = new HotSwapPassFactory("GlobalVarRefCleanupPassFactory", false) { @Override protected HotSwapCompilerPass createInternal( AbstractCompiler compiler) { return new GlobalVarRefCleanupPass(compiler); } }; /** * A CleanupPass implementation that will remove stored scopes from the * MemoizedScopeCreator of the compiler instance for a the hot swapped script. * <p> * This pass will also clear out Source Nodes of Function Types declared on * Vars tracked by MemoizedScopeCreator */ static class MemoizedScopeCleanupPass implements HotSwapCompilerPass { private final AbstractCompiler compiler; public MemoizedScopeCleanupPass(AbstractCompiler compiler) { this.compiler

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>ClassReference(propertyName); } @Override public String extractClassNameIfProvide(Node node, Node parent) { return nextConvention.extractClassNameIfProvide(node, parent); } @Override public String extractClassNameIfRequire(Node node, Node parent) { return nextConvention.extractClassNameIfRequire(node, parent); } @Override public String getExportPropertyFunction() { return nextConvention.getExportPropertyFunction(); } @Override public String getExportSymbolFunction() { return nextConvention.getExportSymbolFunction(); } @Override public List<String> identifyTypeDeclarationCall(Node n) { return nextConvention.identifyTypeDeclarationCall(n); } @Override public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type) { nextConvention.applySubclassRelationship( parentCtor, childCtor, type); } @Override public String getAbstractMethodName() { return nextConvention.getAbstractMethodName(); } @Override public String getSingletonGetterClassName(Node callNode) { return nextConvention.getSingletonGetterClassName(callNode); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { nextConvention.applySingletonGetter( functionType, getterType, objectType); } @Override public boolean isInlinableFunction(Node n) { return nextConvention.isInlinableFunction(n); } @Override public DelegateRelationship getDelegateRelationship(Node callNode) { return nextConvention.getDelegateRelationship(callNode); } @Override public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate) { nextConvention.applyDelegateRelationship( delegateSuperclass, delegateBase, delegator, delegateProxy, findDelegate); } @Override public String getDelegateSuperclassName() { return nextConvention.getDelegateSuperclassName(); } @Override public void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions) { nextConvention.checkForCallingConventionDefiningCalls( n, delegateCallingConventions); } @Override public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions) { nextConvention.defineDelegateProxyPrototypeProperties( registry, scope, delegateProxyPrototypes, delegateCallingConventions); } @Override public String getGlobalObject() { return nextConvention.getGlobalObject(); } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return nextConvention.getAssertionFunctions(); } @Override public Bind describeFunctionBind(Node n) { return describeFunctionBind(n, false); } @Override public Bind describeFunctionBind(Node n, boolean useTypeInfo) { return nextConvention.describeFunctionBind(n, useTypeInfo); } @Override public boolean isPropertyTestFunction(Node call) { return nextConvention.isPropertyTestFunction(call); } @Override public boolean isPrototypeAlias(Node getProp) { return false; } @Override public ObjectLiteralCast getObjectLiteralCast(Node callNode) { return nextConvention.getObjectLiteralCast(callNode); } @Override public Collection<String> getIndirectly

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>DeclaredProperties() { return nextConvention.getIndirectlyDeclaredProperties(); } } /** * The default coding convention. * Should be at the bottom of all proxy chains. */ private static class DefaultCodingConvention implements CodingConvention { private static final long serialVersionUID = 1L; @Override public boolean isConstant(String variableName) { return false; } @Override public boolean isConstantKey(String variableName) { return false; } @Override public boolean isValidEnumKey(String key) { return key != null && key.length() > 0; } @Override public boolean isOptionalParameter(Node parameter) { // be as lax as possible, but this must be mutually exclusive from // var_args parameters. return !isVarArgsParameter(parameter); } @Override public boolean isVarArgsParameter(Node parameter) { // be as lax as possible return parameter.getParent().getLastChild() == parameter; } @Override public boolean isExported(String name, boolean local) { return local && name.startsWith("$super"); } @Override public boolean isExported(String name) { return isExported(name, false) || isExported(name, true); } @Override public boolean isPrivate(String name) { return false; } @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { return null; } @Override public boolean isSuperClassReference(String propertyName) { return false; } @Override public String extractClassNameIfProvide(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String extractClassNameIfRequire(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String getExportPropertyFunction() { return null; } @Override public String getExportSymbolFunction() { return null; } @Override public List<String> identifyTypeDeclarationCall(Node n) { return null; } @Override public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type) { // do nothing } @Override public String getAbstractMethodName() { return null; } @Override public String getSingletonGetterClassName(Node callNode) { return null; } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { // do nothing. } @Override public boolean isInlinableFunction(Node n) { Preconditions.checkState(n.isFunction()); return true; } @Override public DelegateRelationship getDelegateRelationship(Node callNode) { return null; } @Override public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate) { // do nothing. } @Override public String getDelegateSuperclassName() { return null; } @Override public void checkForCallingConventionDefiningCalls(Node n, Map<String, String> delegateCallingConventions) { // do nothing. } @Override public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.debugging.sourcemap.FilePosition; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.nio.charset.Charset; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; /** * CodePrinter prints out JS code in either pretty format or compact format. * * @see CodeGenerator */ class CodePrinter { // The number of characters after which we insert a line break in the code static final int DEFAULT_LINE_LENGTH_THRESHOLD = 500; // There are two separate CodeConsumers, one for pretty-printing and // another for compact printing. // There are two implementations because the CompactCodePrinter // potentially has a very different implementation to the pretty // version. private abstract static class MappedCodePrinter extends CodeConsumer { final private Deque<Mapping> mappings; final private List<Mapping> allMappings; final private boolean createSrcMap; final private SourceMap.DetailLevel sourceMapDetailLevel; protected final StringBuilder code = new StringBuilder(1024); protected final int lineLengthThreshold; protected int lineLength = 0; protected int lineIndex = 0; MappedCodePrinter( int lineLengthThreshold, boolean createSrcMap, SourceMap.DetailLevel sourceMapDetailLevel) { Preconditions.checkState(sourceMapDetailLevel != null); this.lineLengthThreshold = lineLengthThreshold <= 0 ? Integer.MAX_VALUE : lineLengthThreshold; this.createSrcMap = createSrcMap; this.sourceMapDetailLevel = sourceMapDetailLevel; this.mappings = createSrcMap ? new ArrayDeque<Mapping>() : null; this.allMappings = createSrcMap ? new ArrayList<Mapping>() : null; } /** * Maintains a mapping from a given node to the position * in the source code at which its generated form was * placed. This position is relative only to the current * run of the CodeConsumer and will be normalized * later on by the SourceMap. * * @see SourceMap */ private static class Mapping { Node node; FilePosition start; FilePosition end; } /** * Starts the source mapping for the given * node at the current position. */ @Override void startSourceMapping(Node node) { Preconditions.checkState(sourceMapDetailLevel != null); Preconditions.checkState(node != null); if (createSrcMap && node.getSourceFileName() != null && node.getLineno() > 0 && sourceMapDetailLevel.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Lists; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.IR; import com.google.javascript.rhino.JSDocInfoBuilder; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.List; /** * Checks for non side effecting statements such as * <pre> * var s = "this string is " * "continued on the next line but you forgot the +"; * x == foo(); // should that be '='? * foo();; // probably just a stray-semicolon. Doesn't hurt to check though * </p> * and generates warnings. * */ final class CheckSideEffects extends AbstractPostOrderCallback implements HotSwapCompilerPass { static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning( "JSC_USELESS_CODE", "Suspicious code. {0}"); static final String PROTECTOR_FN = "JSCOMPILER_PRESERVE"; private final CheckLevel level; private final List<Node> problemNodes = Lists.newArrayList(); private final AbstractCompiler compiler; private final boolean protectSideEffectFreeCode; CheckSideEffects(AbstractCompiler compiler, CheckLevel level, boolean protectSideEffectFreeCode) { this.compiler = compiler; this.level = level; this.protectSideEffectFreeCode = protectSideEffectFreeCode; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); // Code with hidden side-effect code is common, for example // accessing "el.offsetWidth" forces a reflow in browsers, to allow this // will still allowing local dead code removal in general, // protect the "side-effect free" code in the source. // if (protectSideEffectFreeCode) { protectSideEffects(); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { NodeTraversal.traverse(compiler, scriptRoot, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) {

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.graph; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; /** * A directed graph using linked list within nodes to store edge information. * <p> * This implementation favors directed graph operations inherited from <code> * DirectedGraph</code>. * Operations from <code>Graph</code> would tends to be slower. * * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public class LinkedDirectedGraph<N, E> extends DiGraph<N, E> implements GraphvizGraph { protected final Map<N, LinkedDirectedGraphNode<N, E>> nodes = Maps.newHashMap(); @Override public SubGraph<N, E> newSubGraph() { return new SimpleSubGraph<N, E>(this); } public static <N, E> LinkedDirectedGraph<N, E> createWithoutAnnotations() { return new LinkedDirectedGraph<N, E>(false, false); } public static <N, E> LinkedDirectedGraph<N, E> createWithNodeAnnotations() { return new LinkedDirectedGraph<N, E>(true, false); } public static <N, E> LinkedDirectedGraph<N, E> createWithEdgeAnnotations() { return new LinkedDirectedGraph<N, E>(false, true); } public static <N, E> LinkedDirectedGraph<N, E> create() { return new LinkedDirectedGraph<N, E>(true, true); } private final boolean useNodeAnnotations; private final boolean useEdgeAnnotations; protected LinkedDirectedGraph( boolean useNodeAnnotations, boolean useEdgeAnnotations) { this.useNodeAnnotations = useNodeAnnotations; this.useEdgeAnnotations = useEdgeAnnotations; } @Override public void connect(N srcValue, E edgeValue, N destValue) { LinkedDirectedGraphNode<N, E> src = getNodeOrFail(srcValue); LinkedDirectedGraphNode<N, E> dest = getNodeOrFail(destValue); LinkedDirectedGraphEdge<N, E> edge = useEdgeAnnotations ? new AnnotatedLinkedDirectedGraphEdge<N, E>(src, edgeValue, dest) : new LinkedDirectedGraphEdge<N, E>(src,

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> edgeValue, dest); src.getOutEdges().add(edge); dest.getInEdges().add(edge); } @Override public void disconnect(N n1, N n2) { disconnectInDirection(n1, n2); disconnectInDirection(n2, n1); } @Override public void disconnectInDirection(N srcValue, N destValue) { LinkedDirectedGraphNode<N, E> src = getNodeOrFail(srcValue); LinkedDirectedGraphNode<N, E> dest = getNodeOrFail(destValue); for (DiGraphEdge<?, E> edge : getDirectedGraphEdges(srcValue, destValue)) { src.getOutEdges().remove(edge); dest.getInEdges().remove(edge); } } @Override public Iterable<DiGraphNode<N, E>> getDirectedGraphNodes() { return Collections.<DiGraphNode<N, E>>unmodifiableCollection( nodes.values()); } @Override public DiGraphNode<N, E> getDirectedGraphNode(N nodeValue) { return nodes.get(nodeValue); } @Override public GraphNode<N, E> getNode(N nodeValue) { return getDirectedGraphNode(nodeValue); } @Override public List<DiGraphEdge<N, E>> getInEdges(N nodeValue) { LinkedDirectedGraphNode<N, E> node = getNodeOrFail(nodeValue); return Collections.<DiGraphEdge<N, E>>unmodifiableList(node.getInEdges()); } @Override public List<DiGraphEdge<N, E>> getOutEdges(N nodeValue) { LinkedDirectedGraphNode<N, E> node = getNodeOrFail(nodeValue); return Collections.<DiGraphEdge<N, E>>unmodifiableList(node.getOutEdges()); } @Override public DiGraphNode<N, E> createDirectedGraphNode(N nodeValue) { LinkedDirectedGraphNode<N, E> node = nodes.get(nodeValue); if (node == null) { node = useNodeAnnotations ? new AnnotatedLinkedDirectedGraphNode<N, E>(nodeValue) : new LinkedDirectedGraphNode<N, E>(nodeValue); nodes.put(nodeValue, node); } return node; } @Override public List<GraphEdge<N, E>> getEdges(N n1, N n2) { // Since this is a method from a generic graph, edges from both // directions must be added to the returning list. List<DiGraphEdge<N, E>> forwardEdges = getDirectedGraphEdges(n1, n2); List<DiGraphEdge<N, E>> backwardEdges = getDirectedGraphEdges(n2, n1); int totalSize = forwardEdges.size() + backwardEdges.size(); List<GraphEdge<N, E>> edges = Lists.newArrayListWithCapacity(totalSize); edges.addAll(forwardEdges); edges.addAll(backwardEdges); return edges; } @Override public GraphEdge<N, E> getFirstEdge(N n1, N n2) { DiGraphNode<N, E> dNode1 = getNodeOrFail(n1); DiGraphNode<N, E> dNode2 = getNodeOrFail(n2); for (DiGraphEdge<N, E> outEdge : dNode1.getOutEdges())

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> { if (outEdge.getDestination() == dNode2) { return outEdge; } } for (DiGraphEdge<N, E> outEdge : dNode2.getOutEdges()) { if (outEdge.getDestination() == dNode1) { return outEdge; } } return null; } @Override public GraphNode<N, E> createNode(N value) { return createDirectedGraphNode(value); } @Override public List<DiGraphEdge<N, E>> getDirectedGraphEdges(N n1, N n2) { DiGraphNode<N, E> dNode1 = getNodeOrFail(n1); DiGraphNode<N, E> dNode2 = getNodeOrFail(n2); List<DiGraphEdge<N, E>> edges = Lists.newArrayList(); for (DiGraphEdge<N, E> outEdge : dNode1.getOutEdges()) { if (outEdge.getDestination() == dNode2) { edges.add(outEdge); } } return edges; } @Override public boolean isConnectedInDirection(N n1, N n2) { return isConnectedInDirection(n1, Predicates.<E>alwaysTrue(), n2); } @Override public boolean isConnectedInDirection(N n1, E edgeValue, N n2) { return isConnectedInDirection(n1, Predicates.equalTo(edgeValue), n2); } private boolean isConnectedInDirection(N n1, Predicate<E> edgeMatcher, N n2) { // Verify the nodes. DiGraphNode<N, E> dNode1 = getNodeOrFail(n1); DiGraphNode<N, E> dNode2 = getNodeOrFail(n2); for (DiGraphEdge<N, E> outEdge : dNode1.getOutEdges()) { if (outEdge.getDestination() == dNode2 && edgeMatcher.apply(outEdge.getValue())) { return true; } } return false; } @Override public List<DiGraphNode<N, E>> getDirectedPredNodes(N nodeValue) { return getDirectedPredNodes(nodes.get(nodeValue)); } @Override public List<DiGraphNode<N, E>> getDirectedSuccNodes(N nodeValue) { return getDirectedSuccNodes(nodes.get(nodeValue)); } @Override public List<DiGraphNode<N, E>> getDirectedPredNodes( DiGraphNode<N, E> dNode) { if (dNode == null) { throw new IllegalArgumentException(dNode + " is null"); } List<DiGraphNode<N, E>> nodeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : dNode.getInEdges()) { nodeList.add(edge.getSource()); } return nodeList; } @Override public List<DiGraphNode<N, E>> getDirectedSuccNodes( DiGraphNode<N, E> dNode) { if (dNode == null) { throw new IllegalArgumentException(dNode + " is null"); } List<DiGraphNode<N, E>> nodeList = Lists.newArrayList(); for (DiGraphEdge<N, E> edge : dNode.getOutEdges()) { nodeList.add

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>(edge.getDestination()); } return nodeList; } @Override public List<GraphvizEdge> getGraphvizEdges() { List<GraphvizEdge> edgeList = Lists.newArrayList(); for (LinkedDirectedGraphNode<N, E> node : nodes.values()) { for (DiGraphEdge<N, E> edge : node.getOutEdges()) { edgeList.add((LinkedDirectedGraphEdge<N, E>) edge); } } return edgeList; } @Override public List<GraphvizNode> getGraphvizNodes() { List<GraphvizNode> nodeList = Lists.newArrayListWithCapacity(nodes.size()); for (LinkedDirectedGraphNode<N, E> node : nodes.values()) { nodeList.add(node); } return nodeList; } @Override public String getName() { return "LinkedGraph"; } @Override public boolean isDirected() { return true; } @Override public Collection<GraphNode<N, E>> getNodes() { return Collections.<GraphNode<N, E>>unmodifiableCollection(nodes.values()); } @Override public List<GraphNode<N, E>> getNeighborNodes(N value) { DiGraphNode<N, E> node = getDirectedGraphNode(value); return getNeighborNodes(node); } public List<GraphNode<N, E>> getNeighborNodes(DiGraphNode<N, E> node) { List<GraphNode<N, E>> result = Lists.newArrayList(); for (Iterator<GraphNode<N, E>> i = ((LinkedDirectedGraphNode<N, E>) node).neighborIterator();i.hasNext();) { result.add(i.next()); } return result; } @Override public Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value) { LinkedDirectedGraphNode<N, E> node = nodes.get(value); Preconditions.checkNotNull(node); return node.neighborIterator(); } @Override public List<GraphEdge<N, E>> getEdges() { List<GraphEdge<N, E>> result = Lists.newArrayList(); for (DiGraphNode<N, E> node : nodes.values()) { for (DiGraphEdge<N, E> edge : node.getOutEdges()) { result.add(edge); } } return Collections.unmodifiableList(result); } @Override public int getNodeDegree(N value) { DiGraphNode<N, E> node = getNodeOrFail(value); return node.getInEdges().size() + node.getOutEdges().size(); } /** * A directed graph node that stores outgoing edges and incoming edges as an * list within the node itself. */ static class LinkedDirectedGraphNode<N, E> implements DiGraphNode<N, E>, GraphvizNode { List<DiGraphEdge<N, E>> inEdgeList = Lists.newArrayList(); List<DiGraphEdge<N, E>> outEdgeList = Lists.newArrayList(); protected final N value; /** * Constructor * * @param nodeValue Node's value. */ LinkedDirectedGraphNode(N nodeValue) { this.value = nodeValue; } @Override public N getValue() { return value; } @Override public <A extends Annotation>

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> A getAnnotation() { throw new UnsupportedOperationException( "Graph initialized with node annotations turned off"); } @Override public void setAnnotation(Annotation data) { throw new UnsupportedOperationException( "Graph initialized with node annotations turned off"); } @Override public String getColor() { return "white"; } @Override public String getId() { return "LDN" + hashCode(); } @Override public String getLabel() { return value != null ? value.toString() : "null"; } @Override public String toString() { return getLabel(); } @Override public List<DiGraphEdge<N, E>> getInEdges() { return inEdgeList; } @Override public List<DiGraphEdge<N, E>> getOutEdges() { return outEdgeList; } private Iterator<GraphNode<N, E>> neighborIterator() { return new NeighborIterator(); } private class NeighborIterator implements Iterator<GraphNode<N, E>> { private final Iterator<DiGraphEdge<N, E>> in = inEdgeList.iterator(); private final Iterator<DiGraphEdge<N, E>> out = outEdgeList.iterator(); @Override public boolean hasNext() { return in.hasNext() || out.hasNext(); } @Override public GraphNode<N, E> next() { boolean isOut = !in.hasNext(); Iterator<DiGraphEdge<N, E>> curIterator = isOut ? out : in; DiGraphEdge<N, E> s = curIterator.next(); return isOut ? s.getDestination() : s.getSource(); } @Override public void remove() { throw new UnsupportedOperationException("Remove not supported."); } } } /** * A directed graph node with annotations. */ static class AnnotatedLinkedDirectedGraphNode<N, E> extends LinkedDirectedGraphNode<N, E> { protected Annotation annotation; /** * @param nodeValue Node's value. */ AnnotatedLinkedDirectedGraphNode(N nodeValue) { super(nodeValue); } @SuppressWarnings("unchecked") @Override public <A extends Annotation> A getAnnotation() { return (A) annotation; } @Override public void setAnnotation(Annotation data) { annotation = data; } } /** * A directed graph edge that stores the source and destination nodes at each * edge. */ static class LinkedDirectedGraphEdge<N, E> implements DiGraphEdge<N, E>, GraphvizEdge { private DiGraphNode<N, E> sourceNode; private DiGraphNode<N, E> destNode; protected final E value; /** * Constructor. * * @param edgeValue Edge Value. */ LinkedDirectedGraphEdge(DiGraphNode<N, E> sourceNode, E edgeValue, DiGraphNode<N, E> destNode) { this.value = edgeValue; this.sourceNode = sourceNode; this.destNode = destNode; } @Override public DiGraphNode<N, E> getSource() { return sourceNode; } @Override public DiGraphNode<N, E> getDestination() { return destNode; } @Override public void setDestination(DiGraphNode<N, E> node) { destNode = node; } @Override

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> public void setSource(DiGraphNode<N, E> node) { sourceNode = node; } @Override public E getValue() { return value; } @Override public <A extends Annotation> A getAnnotation() { throw new UnsupportedOperationException( "Graph initialized with edge annotations turned off"); } @Override public void setAnnotation(Annotation data) { throw new UnsupportedOperationException( "Graph initialized with edge annotations turned off"); } @Override public String getColor() { return "black"; } @Override public String getLabel() { return value != null ? value.toString() : "null"; } @Override public String getNode1Id() { return ((LinkedDirectedGraphNode<N, E>) sourceNode).getId(); } @Override public String getNode2Id() { return ((LinkedDirectedGraphNode<N, E>) destNode).getId(); } @Override public String toString() { return sourceNode.toString() + " -> " + destNode.toString(); } @Override public GraphNode<N, E> getNodeA() { return sourceNode; } @Override public GraphNode<N, E> getNodeB() { return destNode; } } /** * A directed graph edge that stores the source and destination nodes at each * edge. */ static class AnnotatedLinkedDirectedGraphEdge<N, E> extends LinkedDirectedGraphEdge<N, E> { protected Annotation annotation; /** * Constructor. * * @param edgeValue Edge Value. */ AnnotatedLinkedDirectedGraphEdge(DiGraphNode<N, E> sourceNode, E edgeValue, DiGraphNode<N, E> destNode) { super(sourceNode, edgeValue, destNode); } @SuppressWarnings("unchecked") @Override public <A extends Annotation> A getAnnotation() { return (A) annotation; } @Override public void setAnnotation(Annotation data) { annotation = data; } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Cake|null) where only Cake is // allowed, that doesn't mean we should invalidate all Cakes. private final List<TypeMismatch> mismatches = Lists.newArrayList(); // User warnings private static final String FOUND_REQUIRED = "{0}\n" + "found : {1}\n" + "required: {2}"; static final DiagnosticType INVALID_CAST = DiagnosticType.warning("JSC_INVALID_CAST", "invalid cast - must be a subtype or supertype\n" + "from: {0}\n" + "to : {1}"); static final DiagnosticType TYPE_MISMATCH_WARNING = DiagnosticType.warning( "JSC_TYPE_MISMATCH", "{0}"); static final DiagnosticType MISSING_EXTENDS_TAG_WARNING = DiagnosticType.warning( "JSC_MISSING_EXTENDS_TAG", "Missing @extends tag on type {0}"); static final DiagnosticType DUP_VAR_DECLARATION = DiagnosticType.warning("JSC_DUP_VAR_DECLARATION", "variable {0} redefined with type {1}, " + "original definition at {2}:{3} with type {4}"); static final DiagnosticType HIDDEN_PROPERTY_MISMATCH = DiagnosticType.warning("JSC_HIDDEN_PROPERTY_MISMATCH", "mismatch of the {0} property type and the type " + "of the property it overrides from superclass {1}\n" + "original: {2}\n" + "override: {3}"); static final DiagnosticType INTERFACE_METHOD_NOT_IMPLEMENTED = DiagnosticType.warning( "JSC_INTERFACE_METHOD_NOT_IMPLEMENTED", "property {0} on interface {1} is not implemented by type {2}"); static final DiagnosticType HIDDEN_INTERFACE_PROPERTY_MISMATCH = DiagnosticType.warning( "JSC_HIDDEN_INTERFACE_PROPERTY_MISMATCH", "mismatch of the {0} property type and the type " + "of the property it overrides from interface {1}\n" + "original: {2}\n" + "override: {3}"); static final DiagnosticType UNKNOWN_TYPEOF_VALUE = DiagnosticType.warning("JSC_UNKNOWN_TYPEOF_VALUE", "unknown type: {0}"); static final DiagnosticGroup ALL_DIAGNOSTICS = new DiagnosticGroup( INVALID_CAST, TYPE_MISMATCH_WARNING, MISSING_EXTENDS_TAG_WARNING, DUP_VAR_DECLARATION, HIDDEN_PROPERTY_MISMATCH, INTERFACE_METHOD_NOT_IMPLEMENTED, HIDDEN_INTERFACE_PROPERTY_MISMATCH, UNKNOWN_TYPEOF_VALUE); TypeValidator(AbstractCompiler compiler) { this.compiler = compiler; this.typeRegistry = compiler.getTypeRegistry(); this.allValueTypes = typeRegistry.createUnionType( STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE, NULL_TYPE, VOID_TYPE); this.nullOrUndefined = typeRegistry.createUnionType( NULL_TYPE, VOID_TYPE); } /** * Gets a list of type violations. * * For each violation, one element is the expected type and the other is * the type that is actually found. Order is not significant. */ Iterable<TypeMismatch> getMismatches() { return mismatches; } void setShouldReport(boolean report) { this.shouldReport = report;

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.graph.LatticeElement; import java.util.List; /** * Defines a way join a list of LatticeElements. */ interface JoinOp<L extends LatticeElement> extends Function<List<L>, L> { /** * An implementation of {@code JoinOp} that makes it easy to join to * lattice elements at a time. */ static abstract class BinaryJoinOp<L extends LatticeElement> implements JoinOp<L> { @Override public final L apply(List<L> values) { Preconditions.checkArgument(!values.isEmpty()); int size = values.size(); if (size == 1) { return values.get(0); } else if (size == 2) { return apply(values.get(0), values.get(1)); } else { int mid = computeMidPoint(size); return apply( apply(values.subList(0, mid)), apply(values.subList(mid, size))); } } /** * Creates a new lattice that will be the join of two input lattices. * * @return The join of {@code latticeA} and {@code latticeB}. */ abstract L apply(L latticeA, L latticeB); /** * Finds the midpoint of a list. The function will favor two lists of * even length instead of two lists of the same odd length. The list * must be at least length two. * * @param size Size of the list. */ static int computeMidPoint(int size) { int midpoint = size >>> 1; if (size > 4) { /* Any list longer than 4 should prefer an even split point * over the true midpoint, so that [0,6] splits at 2, not 3. */ midpoint &= -2; // (0xfffffffe) clears low bit so midpoint is even } return midpoint; } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> replacePlaceHolders(ScriptRuntime.getMessage0("msg.bad.jsdoc.tag")), BAD_JSDOC_ANNOTATION, // Type annotation errors. Pattern.compile("^Bad type annotation.*"), TYPE_PARSE_ERROR); } public static com.google.javascript.rhino.head.ErrorReporter forNewRhino(AbstractCompiler compiler) { return new NewRhinoErrorReporter(compiler); } public static ErrorReporter forOldRhino(AbstractCompiler compiler) { return new OldRhinoErrorReporter(compiler); } void warningAtLine(String message, String sourceName, int line, int lineOffset) { compiler.report( makeError(message, sourceName, line, lineOffset, CheckLevel.WARNING)); } void errorAtLine(String message, String sourceName, int line, int lineOffset) { compiler.report( makeError(message, sourceName, line, lineOffset, CheckLevel.ERROR)); } private JSError makeError(String message, String sourceName, int line, int lineOffset, CheckLevel defaultLevel) { // Try to see if the message is one of the rhino errors we want to // expose as DiagnosticType by matching it with the regex key. for (Entry<Pattern, DiagnosticType> entry : typeMap.entrySet()) { if (entry.getKey().matcher(message).matches()) { return JSError.make( sourceName, line, lineOffset, entry.getValue(), message); } } return JSError.make(sourceName, line, lineOffset, defaultLevel, PARSE_ERROR, message); } private static class OldRhinoErrorReporter extends RhinoErrorReporter implements ErrorReporter { private OldRhinoErrorReporter(AbstractCompiler compiler) { super(compiler); } @Override public void error(String message, String sourceName, int line, int lineOffset) { super.errorAtLine(message, sourceName, line, lineOffset); } @Override public void warning(String message, String sourceName, int line, int lineOffset) { super.warningAtLine(message, sourceName, line, lineOffset); } } private static class NewRhinoErrorReporter extends RhinoErrorReporter implements com.google.javascript.rhino.head.ast.IdeErrorReporter { private NewRhinoErrorReporter(AbstractCompiler compiler) { super(compiler); } @Override public com.google.javascript.rhino.head.EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset) { return new com.google.javascript.rhino.head.EvaluatorException( message, sourceName, line, lineSource, lineOffset); } @Override public void error(String message, String sourceName, int line, String sourceLine, int lineOffset) { super.errorAtLine(message, sourceName, line, lineOffset); } @Override public void error(String message, String sourceName, int offset, int length) { int line = 1; int column = 0; SourceFile file = this.compiler.getSourceFileByName(sourceName); if (file != null) { line = file.getLineOfOffset(offset); column = file.getColumnOfOffset(offset); } super.errorAtLine(message, sourceName, line, column); } @

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>NOT_ASSIGNABLE_ERROR", "@define variable {0} cannot be reassigned due to code at {1}."); private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE = new MessageFormat("line {0} of {1}"); /** * Create a pass that overrides define constants. * * TODO(nicksantos): Write a builder to help JSCompiler induce * {@code replacements} from command-line flags * * @param replacements A hash table of names of defines to their replacements. * All replacements <b>must</b> be literals. */ ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) { this.compiler = compiler; dominantReplacements = replacements; } /** * Injects a pre-computed global namespace, so that the same namespace * can be re-used for multiple check passes. Returns {@code this} for * easy chaining. */ ProcessDefines injectNamespace(GlobalNamespace namespace) { this.namespace = namespace; return this; } @Override public void process(Node externs, Node root) { if (namespace == null) { namespace = new GlobalNamespace(compiler, root); } overrideDefines(collectDefines(root, namespace)); } private void overrideDefines(Map<String, DefineInfo> allDefines) { boolean changed = false; for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) { String defineName = def.getKey(); DefineInfo info = def.getValue(); Node inputValue = dominantReplacements.get(defineName); Node finalValue = inputValue != null ? inputValue : info.getLastValue(); if (finalValue != info.initialValue) { info.initialValueParent.replaceChild( info.initialValue, finalValue.cloneTree()); compiler.addToDebugLog("Overriding @define variable " + defineName); changed = changed || finalValue.getType() != info.initialValue.getType() || !finalValue.isEquivalentTo(info.initialValue); } } if (changed) { compiler.reportCodeChange(); } Set<String> unusedReplacements = dominantReplacements.keySet(); unusedReplacements.removeAll(allDefines.keySet()); unusedReplacements.removeAll(KNOWN_DEFINES); for (String unknownDefine : unusedReplacements) { compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine)); } } private static String format(MessageFormat format, Object... params) { return format.format(params); } /** * Only defines of literal number, string, or boolean are supported. */ private boolean isValidDefineType(JSTypeExpression expression) { JSType type = expression.evaluate(null, compiler.getTypeRegistry()); return !type.isUnknownType() && type.isSubtype( compiler.getTypeRegistry().getNativeType( JSTypeNative.NUMBER_STRING_BOOLEAN)); } /** * Finds all defines, and creates a {@link DefineInfo} data structure for * each one. * @return A map of {@link DefineInfo} structures, keyed by name. */ private Map<String, DefineInfo> collectDefines(Node root, GlobalNamespace namespace) { // Find all the global names with a @define annotation List<Name> allDefines = Lists.newArrayList(); for (Name name : namespace.getNameIndex().values

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>()) { Ref decl = name.getDeclaration(); if (name.docInfo != null && name.docInfo.isDefine()) { // Process defines should not depend on check types being enabled, // so we look for the JSDoc instead of the inferred type. if (isValidDefineType(name.docInfo.getType())) { allDefines.add(name); } else { JSError error = JSError.make( decl.getSourceName(), decl.node, INVALID_DEFINE_TYPE_ERROR); compiler.report(error); } } else { for (Ref ref : name.getRefs()) { if (ref == decl) { // Declarations were handled above. continue; } Node n = ref.node; Node parent = ref.node.getParent(); JSDocInfo info = n.getJSDocInfo(); if (info == null && parent.isVar() && parent.hasOneChild()) { info = parent.getJSDocInfo(); } if (info != null && info.isDefine()) { allDefines.add(name); break; } } } } CollectDefines pass = new CollectDefines(compiler, allDefines); NodeTraversal.traverse(compiler, root, pass); return pass.getAllDefines(); } /** * Finds all assignments to @defines, and figures out the last value of * the @define. */ private static final class CollectDefines implements Callback { private final AbstractCompiler compiler; private final Map<String, DefineInfo> assignableDefines; private final Map<String, DefineInfo> allDefines; private final Map<Node, RefInfo> allRefInfo; // A hack that allows us to remove ASSIGN/VAR statements when // we're currently visiting one of the children of the assign. private Node lvalueToRemoveLater = null; // A stack tied to the node traversal, to keep track of whether // we're in a conditional block. If 1 is at the top, assignment to // a define is allowed. Otherwise, it's not allowed. private final Deque<Integer> assignAllowed; CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) { this.compiler = compiler; this.allDefines = Maps.newHashMap(); assignableDefines = Maps.newHashMap(); assignAllowed = new ArrayDeque<Integer>(); assignAllowed.push(1); // Create a map of references to defines keyed by node for easy lookup allRefInfo = Maps.newHashMap(); for (Name name : listOfDefines) { Ref decl = name.getDeclaration(); if (decl != null) { allRefInfo.put(decl.node, new RefInfo(decl, name)); } for (Ref ref : name.getRefs()) { if (ref == decl) { // Declarations were handled above. continue; } // If there's a TWIN def, only put one of the twins in. if (ref.getTwin() == null || !ref.getTwin().isSet()) { allRefInfo.put(ref.node, new RefInfo(ref, name)); } } } } /** * Get a map of {@link DefineInfo} structures, keyed by the name of * the define. */ Map<String, DefineInfo> getAllDefines() { return allDefines; } /** * Keeps track of whether the traversal is in a conditional branch.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> } } /** * Creates a node traversal using the specified callback interface. */ public NodeTraversal(AbstractCompiler compiler, Callback cb) { this(compiler, cb, new SyntacticScopeCreator(compiler)); } /** * Creates a node traversal using the specified callback interface * and the scope creator. */ public NodeTraversal(AbstractCompiler compiler, Callback cb, ScopeCreator scopeCreator) { this.callback = cb; if (cb instanceof ScopedCallback) { this.scopeCallback = (ScopedCallback) cb; } this.compiler = compiler; this.inputId = null; this.sourceName = ""; this.scopeCreator = scopeCreator; } private void throwUnexpectedException(Exception unexpectedException) { // If there's an unexpected exception, try to get the // line number of the code that caused it. String message = unexpectedException.getMessage(); // TODO(user): It is possible to get more information if curNode or // its parent is missing. We still have the scope stack in which it is still // very useful to find out at least which function caused the exception. if (inputId != null) { message = unexpectedException.getMessage() + "\n" + formatNodeContext("Node", curNode) + (curNode == null ? "" : formatNodeContext("Parent", curNode.getParent())); } compiler.throwInternalError(message, unexpectedException); } private String formatNodeContext(String label, Node n) { if (n == null) { return " " + label + ": NULL"; } return " " + label + "(" + n.toString(false, false, false) + "): " + formatNodePosition(n); } /** * Traverses a parse tree recursively. */ public void traverse(Node root) { try { inputId = NodeUtil.getInputId(root); sourceName = ""; curNode = root; pushScope(root); traverseBranch(root, null); popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } public void traverseRoots(Node ... roots) { traverseRoots(Lists.newArrayList(roots)); } public void traverseRoots(List<Node> roots) { if (roots.isEmpty()) { return; } try { Node scopeRoot = roots.get(0).getParent(); Preconditions.checkState(scopeRoot != null); inputId = NodeUtil.getInputId(scopeRoot); sourceName = ""; curNode = scopeRoot; pushScope(scopeRoot); for (Node root : roots) { Preconditions.checkState(root.getParent() == scopeRoot); traverseBranch(root, scopeRoot); } popScope(); } catch (Exception unexpectedException) { throwUnexpectedException(unexpectedException); } } private static final String MISSING_SOURCE = "[source unknown]"; private String formatNodePosition(Node n) { String sourceFileName = getBestSourceFileName(n); if (sourceFileName == null) { return MISSING_SOURCE + "\n"; } int lineNumber = n.getLineno(); int columnNumber = n.getCharno(); String src = compiler.getSourceLine(sourceFileName, lineNumber); if (src == null) { src = MISSING_SOURCE; } return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n" + src

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> public Node getCurrentNode() { return curNode; } /** * Traverses a node recursively. */ public static void traverse( AbstractCompiler compiler, Node root, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverse(root); } /** * Traverses a list of node trees. */ public static void traverseRoots( AbstractCompiler compiler, List<Node> roots, Callback cb) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } public static void traverseRoots( AbstractCompiler compiler, Callback cb, Node ... roots) { NodeTraversal t = new NodeTraversal(compiler, cb); t.traverseRoots(roots); } /** * Traverses a branch. */ @SuppressWarnings("fallthrough") private void traverseBranch(Node n, Node parent) { int type = n.getType(); if (type == Token.SCRIPT) { inputId = n.getInputId(); sourceName = getSourceName(n); } curNode = n; if (!callback.shouldTraverse(this, n, parent)) return; switch (type) { case Token.FUNCTION: traverseFunction(n, parent); break; default: for (Node child = n.getFirstChild(); child != null; ) { // child could be replaced, in which case our child node // would no longer point to the true next Node next = child.getNext(); traverseBranch(child, n); child = next; } break; } curNode = n; callback.visit(this, n, parent); } /** * Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.isFunction()); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression names are only accessible within the function // scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverseBranch(args, n); // Body Preconditions.checkState(body.getNext() == null && body.isBlock(), body); traverseBranch(body, n); popScope(); } /** Examines the functions stack for the last instance of a function node. */ @SuppressWarnings("unchecked") public Node getEnclosingFunction() { if (scopes.size() + scopeRoots.size() < 2) { return null; } else { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } } /** Creates a new scope (e.g. when entering a function). */ private void pushScope(Node node) { Preconditions.checkState(curNode != null); scopeRoots.push(node); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Creates a

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> new scope (e.g. when entering a function). */ private void pushScope(Scope s) { Preconditions.checkState(curNode != null); scopes.push(s); cfgs.push(null); if (scopeCallback != null) { scopeCallback.enterScope(this); } } /** Pops back to the previous scope (e.g. when leaving a function). */ private void popScope() { if (scopeCallback != null) { scopeCallback.exitScope(this); } if (scopeRoots.isEmpty()) { scopes.pop(); } else { scopeRoots.pop(); } cfgs.pop(); } /** Gets the current scope. */ public Scope getScope() { Scope scope = scopes.isEmpty() ? null : scopes.peek(); if (scopeRoots.isEmpty()) { return scope; } Iterator<Node> it = scopeRoots.descendingIterator(); while (it.hasNext()) { scope = scopeCreator.createScope(it.next(), scope); scopes.push(scope); } scopeRoots.clear(); return scope; } /** Gets the control flow graph for the current JS scope. */ public ControlFlowGraph<Node> getControlFlowGraph() { if (cfgs.peek() == null) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true); cfa.process(null, getScopeRoot()); cfgs.pop(); cfgs.push(cfa.getCfg()); } return cfgs.peek(); } /** Returns the current scope's root. */ public Node getScopeRoot() { if (scopeRoots.isEmpty()) { return scopes.peek().getRootNode(); } else { return scopeRoots.peek(); } } /** * Determines whether the traversal is currently in the global scope. */ boolean inGlobalScope() { return getScopeDepth() <= 1; } int getScopeDepth() { return scopes.size() + scopeRoots.size(); } public boolean hasScope() { return !(scopes.isEmpty() && scopeRoots.isEmpty()); } /** Reports a diagnostic (error or warning) */ public void report(Node n, DiagnosticType diagnosticType, String... arguments) { JSError error = JSError.make(getBestSourceFileName(n), n, diagnosticType, arguments); compiler.report(error); } private static String getSourceName(Node n) { String name = n.getSourceFileName(); return name == null ? "" : name; } InputId getInputId() { return inputId; } /** * Creates a JSError during NodeTraversal. * * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public JSError makeError(Node n, CheckLevel level, DiagnosticType type, String... arguments) { return JSError.make(getBestSourceFileName(n), n, level, type, arguments); } /** * Creates a JSError during NodeTraversal. * * @param n Determines the line and char position within the source file name * @param type The DiagnosticType * @param arguments Arguments to be incorporated into the message */ public JSError makeError(Node n, DiagnosticType type, String... arguments) { return JSError.make(

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * John Lenz * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino; import com.google.common.base.Preconditions; import java.util.List; /** * An AST construction helper class * @author johnlenz@google.com (John Lenz) */ public class IR { private IR() {} public static Node empty() { return new Node(Token.EMPTY); } public static Node function(Node name, Node params, Node body) { Preconditions.checkState(name.isName()); Preconditions.checkState(params.isParamList()); Preconditions.checkState(body.isBlock()); return new Node(Token.FUNCTION, name, params, body); } public static Node paramList() { return new Node(Token.PARAM_LIST); } public static Node paramList(Node param) { Preconditions.checkState(param.isName()); return new Node(Token.PARAM_LIST, param); } public static Node paramList(Node ... params) { Node paramList = paramList(); for (Node param : params) { Preconditions.checkState(param.isName()); paramList.addChildToBack(param); } return paramList; } public static Node paramList(List<Node> params) { Node paramList = paramList(); for (Node param : params) { Preconditions.checkState(param.isName()); paramList.addChildToBack(param); } return paramList; } public static Node block() { Node block = new Node(Token.BLOCK); return block; } public static Node block(Node stmt) { Preconditions.checkState(mayBeStatement(stmt

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>)); Node block = new Node(Token.BLOCK, stmt); return block; } public static Node block(Node ... stmts) { Node block = block(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatement(stmt)); block.addChildToBack(stmt); } return block; } public static Node block(List<Node> stmts) { Node paramList = block(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatement(stmt)); paramList.addChildToBack(stmt); } return paramList; } private static Node blockUnchecked(Node stmt) { return new Node(Token.BLOCK, stmt); } public static Node script() { // TODO(johnlenz): finish setting up the SCRIPT node Node block = new Node(Token.SCRIPT); return block; } public static Node script(Node ... stmts) { Node block = script(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatementNoReturn(stmt)); block.addChildToBack(stmt); } return block; } public static Node script(List<Node> stmts) { Node paramList = script(); for (Node stmt : stmts) { Preconditions.checkState(mayBeStatementNoReturn(stmt)); paramList.addChildToBack(stmt); } return paramList; } public static Node var(Node name, Node value) { Preconditions.checkState(name.isName() && !name.hasChildren()); Preconditions.checkState(mayBeExpression(value)); name.addChildToFront(value); return var(name); } public static Node var(Node name) { Preconditions.checkState(name.isName()); return new Node(Token.VAR, name); } public static Node returnNode() { return new Node(Token.RETURN); } public static Node returnNode(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.RETURN, expr); } public static Node throwNode(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.THROW, expr); } public static Node exprResult(Node expr) { Preconditions.checkState(mayBeExpression(expr)); return new Node(Token.EXPR_RESULT, expr); } public static Node ifNode(Node cond, Node then) { Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState(then.isBlock()); return new Node(Token.IF, cond, then); } public static Node ifNode(Node cond, Node then, Node elseNode) { Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState(then.isBlock()); Preconditions.checkState(elseNode.isBlock()); return new Node(Token.IF, cond, then, elseNode); } public static Node doNode(Node body, Node cond) { Preconditions.checkState(body.isBlock()); Preconditions.checkState(mayBeExpression(cond)); return new Node(Token.DO, body, cond); } public static Node forIn(Node target, Node cond, Node body) { Preconditions.checkState(target.isVar() || mayBeExpression(target)); Preconditions.checkState(mayBeExpression(cond)); Preconditions.checkState

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Str) { this.appNameStr = appNameStr; } /** Record function information */ public boolean recordFunctionInformation; public boolean generateExports; /** Map used in the renaming of CSS class names. */ public CssRenamingMap cssRenamingMap; /** Process instances of goog.testing.ObjectPropertyString. */ boolean processObjectPropertyString; /** Replace id generators */ boolean replaceIdGenerators = true; // true by default for legacy reasons. /** Id generators to replace. */ Set<String> idGenerators; /** Configuration strings */ List<String> replaceStringsFunctionDescriptions; String replaceStringsPlaceholderToken; // A list of strings that should not be used as replacements Set<String> replaceStringsReservedStrings; /** List of properties that we report invalidation errors for. */ Map<String, CheckLevel> propertyInvalidationErrors; /** Transform AMD to CommonJS modules. */ boolean transformAMDToCJSModules = false; /** Rewrite CommonJS modules so that they can be concatenated together. */ boolean processCommonJSModules = false; /** CommonJS module prefix. */ String commonJSModulePathPrefix = ProcessCommonJSModules.DEFAULT_FILENAME_PREFIX; //-------------------------------- // Output options //-------------------------------- /** Output in pretty indented format */ public boolean prettyPrint; /** Line break the output a bit more aggressively */ public boolean lineBreak; /** Prefer line breaks at end of file */ public boolean preferLineBreakAtEndOfFile; /** Prints a separator comment before each JS script */ public boolean printInputDelimiter; /** The string to use as the separator for printInputDelimiter */ public String inputDelimiter = "// Input %num%"; String reportPath; /** Where to save a report of global name usage */ public void setReportPath(String reportPath) { this.reportPath = reportPath; } TracerMode tracer; public TracerMode getTracerMode() { return tracer; } public void setTracerMode(TracerMode mode) { tracer = mode; } private boolean colorizeErrorOutput; public ErrorFormat errorFormat; private ComposeWarningsGuard warningsGuard = new ComposeWarningsGuard(); int summaryDetailLevel = 1; int lineLengthThreshold = CodePrinter.DEFAULT_LINE_LENGTH_THRESHOLD; //-------------------------------- // Special Output Options //-------------------------------- /** * Whether the exports should be made available via {@link Result} after * compilation. This is implicitly true if {@link #externExportsPath} is set. */ private boolean externExports; /** The output path for the created externs file. */ String externExportsPath; String nameReferenceReportPath; /** Where to save a cross-reference report from the name reference graph */ public void setNameReferenceReportPath(String filePath) { nameReferenceReportPath = filePath; } String nameReferenceGraphPath; /** Where to save the name reference graph */ public void setNameReferenceGraphPath(String filePath) { nameReferenceGraphPath = filePath; } //-------------------------------- // Debugging Options //-------------------------------- /** The output path for the source map. */ public String sourceMapOutputPath; /** The detail level for the generated source map. */ public SourceMap.DetailLevel sourceMapDetailLevel = SourceMap.DetailLevel.SYMBOLS; /** The source map file format */ public SourceMap.Format sourceMapFormat = SourceMap.Format.DEFAULT; public List<SourceMap.LocationMapping> sourceMapLocationMappings = Collections.emptyList();

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> false; shadowVariables = false; renamePrefix = null; aliasKeywords = false; collapseProperties = false; collapsePropertiesOnExternTypes = false; collapseObjectLiterals = false; devirtualizePrototypeMethods = false; disambiguateProperties = false; ambiguateProperties = false; anonymousFunctionNaming = AnonymousFunctionNamingPolicy.OFF; exportTestFunctions = false; // Alterations runtimeTypeCheck = false; runtimeTypeCheckLogFunction = null; ignoreCajaProperties = false; syntheticBlockStartMarker = null; syntheticBlockEndMarker = null; locale = null; markAsCompiled = false; removeTryCatchFinally = false; closurePass = false; jqueryPass = false; removeAbstractMethods = true; removeClosureAsserts = false; stripTypes = Collections.emptySet(); stripNameSuffixes = Collections.emptySet(); stripNamePrefixes = Collections.emptySet(); stripTypePrefixes = Collections.emptySet(); customPasses = null; markNoSideEffectCalls = false; defineReplacements = Maps.newHashMap(); tweakProcessing = TweakProcessing.OFF; tweakReplacements = Maps.newHashMap(); moveFunctionDeclarations = false; instrumentationTemplate = null; appNameStr = ""; recordFunctionInformation = false; generateExports = false; cssRenamingMap = null; processObjectPropertyString = false; idGenerators = Collections.emptySet(); replaceStringsFunctionDescriptions = Collections.emptyList(); replaceStringsPlaceholderToken = ""; replaceStringsReservedStrings = Collections.emptySet(); propertyInvalidationErrors = Maps.newHashMap(); // Output printInputDelimiter = false; prettyPrint = false; lineBreak = false; preferLineBreakAtEndOfFile = false; reportPath = null; tracer = TracerMode.OFF; colorizeErrorOutput = false; errorFormat = ErrorFormat.SINGLELINE; debugFunctionSideEffectsPath = null; externExports = false; nameReferenceReportPath = null; nameReferenceGraphPath = null; // Debugging aliasHandler = NULL_ALIAS_TRANSFORMATION_HANDLER; errorHandler = null; } /** * @return Whether to attempt to remove unused class properties */ public boolean isRemoveUnusedClassProperties() { return removeUnusedClassProperties; } /** * @param removeUnusedClassProperties Whether to attempt to remove * unused class properties */ public void setRemoveUnusedClassProperties(boolean removeUnusedClassProperties) { this.removeUnusedClassProperties = removeUnusedClassProperties; } /** * Returns the map of define replacements. */ public Map<String, Node> getDefineReplacements() { return getReplacementsHelper(defineReplacements); } /** * Returns the map of tweak replacements. */ public Map<String, Node> getTweakReplacements() { return getReplacementsHelper(tweakReplacements); } /** * Creates a map of String->Node from a map of String->Number/String/Boolean. */ private static Map<String, Node> getReplacementsHelper( Map<String, Object> source) { Map<String, Node> map = Maps.newHashMap(); for (Map.Entry<String, Object> entry : source.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); if (value instanceof Boolean) { map.put(name, NodeUtil.booleanNode(((Boolean) value

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Variables(Reach reach) { switch (reach) { case ALL: this.removeUnusedVars = true; this.removeUnusedLocalVars = true; break; case LOCAL_ONLY: this.removeUnusedVars = false; this.removeUnusedLocalVars = true; break; case NONE: this.removeUnusedVars = false; this.removeUnusedLocalVars = false; break; default: throw new IllegalStateException("unexpected"); } } /** * Sets the functions whose debug strings to replace. */ public void setReplaceStringsConfiguration( String placeholderToken, List<String> functionDescriptors) { this.replaceStringsPlaceholderToken = placeholderToken; this.replaceStringsFunctionDescriptions = Lists.newArrayList(functionDescriptors); } @Deprecated public void setRewriteNewDateGoogNow(boolean rewrite) { } public void setRemoveAbstractMethods(boolean remove) { this.removeAbstractMethods = remove; } public void setRemoveClosureAsserts(boolean remove) { this.removeClosureAsserts = remove; } /** * If true, name anonymous functions only. All other passes will be skipped. */ public void setNameAnonymousFunctionsOnly(boolean value) { this.nameAnonymousFunctionsOnly = value; } public void setColorizeErrorOutput(boolean colorizeErrorOutput) { this.colorizeErrorOutput = colorizeErrorOutput; } public boolean shouldColorizeErrorOutput() { return colorizeErrorOutput; } /** * If true, chain calls to functions that return this. */ public void setChainCalls(boolean value) { this.chainCalls = value; } /** * If true, accept `const' keyword. */ public void setAcceptConstKeyword(boolean value) { this.acceptConstKeyword = value; } /** * Enable run-time type checking, which adds JS type assertions for debugging. * * @param logFunction A JS function to be used for logging run-time type * assertion failures. */ public void enableRuntimeTypeCheck(String logFunction) { this.runtimeTypeCheck = true; this.runtimeTypeCheckLogFunction = logFunction; } public void disableRuntimeTypeCheck() { this.runtimeTypeCheck = false; } public void setGenerateExports(boolean generateExports) { this.generateExports = generateExports; } public void setCodingConvention(CodingConvention codingConvention) { this.codingConvention = codingConvention; } public CodingConvention getCodingConvention() { return codingConvention; } /** * Sets dependency options. See the DependencyOptions class for more info. * This supersedes manageClosureDependencies. */ public void setDependencyOptions(DependencyOptions options) { Preconditions.checkNotNull(options); this.dependencyOptions = options; } /** * Sort inputs by their goog.provide/goog.require calls, and prune inputs * whose symbols are not required. */ public void setManageClosureDependencies(boolean newVal) { dependencyOptions.setDependencySorting( newVal || dependencyOptions.shouldSortDependencies()); dependencyOptions.setDependencyPruning( newVal || dependencyOptions.shouldPruneDependencies()); dependencyOptions.setMoocherDropping(false); manageClosureDependencies = newVal; } /** * Sort inputs by their goog.provide/goog.require calls. * * @param entryPoints Entry points to the program. Must be goog.provide'd

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> * symbols. Any goog.provide'd symbols that are not a transitive * dependency of the entry points will be deleted. * Files without goog.provides, and their dependencies, * will always be left in. */ public void setManageClosureDependencies(List<String> entryPoints) { Preconditions.checkNotNull(entryPoints); setManageClosureDependencies(true); dependencyOptions.setEntryPoints(entryPoints); } /** * Controls how detailed the compilation summary is. Values: * 0 (never print summary), 1 (print summary only if there are * errors or warnings), 2 (print summary if type checking is on, * see --check_types), 3 (always print summary). The default level * is 1 */ public void setSummaryDetailLevel(int summaryDetailLevel) { this.summaryDetailLevel = summaryDetailLevel; } /** * @deprecated replaced by {@link #setExternExports} */ @Deprecated public void enableExternExports(boolean enabled) { this.externExports = enabled; } public void setExtraAnnotationNames(Set<String> extraAnnotationNames) { this.extraAnnotationNames = Sets.newHashSet(extraAnnotationNames); } public boolean isExternExportsEnabled() { return externExports; } /** * Sets the output charset by name. */ public void setOutputCharset(String charsetName) { this.outputCharset = charsetName; } /** * Sets how goog.tweak calls are processed. */ public void setTweakProcessing(TweakProcessing tweakProcessing) { this.tweakProcessing = tweakProcessing; } public TweakProcessing getTweakProcessing() { return tweakProcessing; } /** * Sets how goog.tweak calls are processed. */ public void setLanguageIn(LanguageMode languageIn) { this.languageIn = languageIn; this.languageOut = languageIn; } public LanguageMode getLanguageIn() { return languageIn; } public LanguageMode getLanguageOut() { return languageOut; } /** * Whether to include "undefined" in the default types. * For example: * "{Object}" is normally "Object|null" becomes "Object|null|undefined" * "{?string}" is normally "string|null" becomes "string|null|undefined" * In either case "!" annotated types excluded both null and undefined. */ public void setLooseTypes(boolean looseTypes) { this.looseTypes = looseTypes; } @Override public Object clone() throws CloneNotSupportedException { CompilerOptions clone = (CompilerOptions) super.clone(); // TODO(bolinfest): Add relevant custom cloning. return clone; } public void setAliasTransformationHandler( AliasTransformationHandler changes) { this.aliasHandler = changes; } public AliasTransformationHandler getAliasTransformationHandler() { return this.aliasHandler; } /** * Set a custom handler for warnings and errors. * * This is mostly used for piping the warnings and errors to * a file behind the scenes. * * If you want to filter warnings and errors, you should use a WarningsGuard. * * If you want to change how warnings and errors are reported to the user, * you should set a ErrorManager on the Compiler. An ErrorManager is * intended to summarize the errors for a single compile job. */ public void set

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Names) { this.gatherCssNames = gatherCssNames; } public void setStripTypes(Set<String> stripTypes) { this.stripTypes = stripTypes; } public void setStripNameSuffixes(Set<String> stripNameSuffixes) { this.stripNameSuffixes = stripNameSuffixes; } public void setStripNamePrefixes(Set<String> stripNamePrefixes) { this.stripNamePrefixes = stripNamePrefixes; } public void setStripTypePrefixes(Set<String> stripTypePrefixes) { this.stripTypePrefixes = stripTypePrefixes; } public void setCustomPasses(Multimap<CustomPassExecutionTime, CompilerPass> customPasses) { this.customPasses = customPasses; } public void setMarkNoSideEffectCalls(boolean markNoSideEffectCalls) { this.markNoSideEffectCalls = markNoSideEffectCalls; } public void setDefineReplacements(Map<String, Object> defineReplacements) { this.defineReplacements = defineReplacements; } public void setTweakReplacements(Map<String, Object> tweakReplacements) { this.tweakReplacements = tweakReplacements; } public void setMoveFunctionDeclarations(boolean moveFunctionDeclarations) { this.moveFunctionDeclarations = moveFunctionDeclarations; } public void setInstrumentationTemplate(String instrumentationTemplate) { this.instrumentationTemplate = instrumentationTemplate; } public void setRecordFunctionInformation(boolean recordFunctionInformation) { this.recordFunctionInformation = recordFunctionInformation; } public void setCssRenamingMap(CssRenamingMap cssRenamingMap) { this.cssRenamingMap = cssRenamingMap; } public void setReplaceStringsFunctionDescriptions(List<String> replaceStringsFunctionDescriptions) { this.replaceStringsFunctionDescriptions = replaceStringsFunctionDescriptions; } public void setReplaceStringsPlaceholderToken(String replaceStringsPlaceholderToken) { this.replaceStringsPlaceholderToken = replaceStringsPlaceholderToken; } public void setReplaceStringsReservedStrings(Set<String> replaceStringsReservedStrings) { this.replaceStringsReservedStrings = replaceStringsReservedStrings; } public void setPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; } public void setLineBreak(boolean lineBreak) { this.lineBreak = lineBreak; } public void setPreferLineBreakAtEndOfFile(boolean lineBreakAtEnd) { this.preferLineBreakAtEndOfFile = lineBreakAtEnd; } public void setPrintInputDelimiter(boolean printInputDelimiter) { this.printInputDelimiter = printInputDelimiter; } public void setInputDelimiter(String inputDelimiter) { this.inputDelimiter = inputDelimiter; } public void setTracer(TracerMode tracer) { this.tracer = tracer; } public void setErrorFormat(ErrorFormat errorFormat) { this.errorFormat = errorFormat; } public void setWarningsGuard(ComposeWarningsGuard warningsGuard) { this.warningsGuard = warningsGuard; } public void setLineLengthThreshold(int lineLengthThreshold) { this.lineLengthThreshold = lineLengthThreshold; } public void setExternExports(boolean externExports) { this.externExports = externExports; } public void setExternExportsPath(String externExportsPath) { this.externExportsPath = externExportsPath; } public void setSourceMapOutputPath(String sourceMapOutput

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Path) { this.sourceMapOutputPath = sourceMapOutputPath; } public void setSourceMapDetailLevel(SourceMap.DetailLevel sourceMapDetailLevel) { this.sourceMapDetailLevel = sourceMapDetailLevel; } public void setSourceMapFormat(SourceMap.Format sourceMapFormat) { this.sourceMapFormat = sourceMapFormat; } public void setSourceMapLocationMappings(List<SourceMap.LocationMapping> sourceMapLocationMappings) { this.sourceMapLocationMappings = sourceMapLocationMappings; } /** * Activates transformation of AMD to CommonJS modules. */ public void setTransformAMDToCJSModules(boolean transformAMDToCJSModules) { this.transformAMDToCJSModules = transformAMDToCJSModules; } /** * Rewrites CommonJS modules so that modules can be concatenated together, * by renaming all globals to avoid conflicting with other modules. */ public void setProcessCommonJSModules(boolean processCommonJSModules) { this.processCommonJSModules = processCommonJSModules; } /** * Sets a path prefix for CommonJS modules. */ public void setCommonJSModulePathPrefix(String commonJSModulePathPrefix) { this.commonJSModulePathPrefix = commonJSModulePathPrefix; } ////////////////////////////////////////////////////////////////////////////// // Enums /** When to do the extra sanity checks */ public static enum LanguageMode { /** * Traditional JavaScript */ ECMASCRIPT3, /** * Shiny new JavaScript */ ECMASCRIPT5, /** * Nitpicky, shiny new JavaScript */ ECMASCRIPT5_STRICT, } /** When to do the extra sanity checks */ static enum DevMode { /** * Don't do any extra sanity checks. */ OFF, /** * After the initial parse */ START, /** * At the start and at the end of all optimizations. */ START_AND_END, /** * After every pass */ EVERY_PASS } public static enum TracerMode { ALL, // Collect all timing and size metrics. RAW_SIZE, // Collect all timing and size metrics, except gzipped size. TIMING_ONLY, // Collect timing metrics only. OFF; // Collect no timing and size metrics. boolean isOn() { return this != OFF; } } public static enum TweakProcessing { OFF, // Do not run the ProcessTweaks pass. CHECK, // Run the pass, but do not strip out the calls. STRIP; // Strip out all calls to goog.tweak.*. public boolean isOn() { return this != OFF; } public boolean shouldStrip() { return this == STRIP; } } /** * A Role Specific Interface for JS Compiler that represents a data holder * object which is used to store goog.scope alias code changes to code made * during a compile. There is no guarantee that individual alias changes are * invoked in the order they occur during compilation, so implementations * should not assume any relevance to the order changes arrive. * <p> * Calls to the mutators are expected to resolve very quickly, so * implementations should not perform expensive operations in the mutator * methods. * * @author tylerg@google.com (Tyler Goodwin) */ public interface AliasTransformationHandler { /** * Builds an AliasTransformation implementation and

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.graph; import java.util.List; /** * A generic directed graph. * * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public abstract class DiGraph<N, E> extends Graph<N, E> { /** * Gets an immutable iterable over all the nodes in the graph. */ public abstract Iterable<DiGraphNode<N, E>> getDirectedGraphNodes(); /** * Gets an immutable list of out edges of the given node. */ public abstract List<DiGraphEdge<N, E>> getOutEdges(N nodeValue); /** * Gets an immutable list of in edges of the given node. */ public abstract List<DiGraphEdge<N, E>> getInEdges(N nodeValue); public abstract List<DiGraphNode<N, E>> getDirectedPredNodes( DiGraphNode<N, E> n); public abstract List<DiGraphNode<N, E>> getDirectedSuccNodes( DiGraphNode<N, E> n); public abstract List<DiGraphNode<N, E>> getDirectedPredNodes(N nodeValue); public abstract List<DiGraphNode<N, E>> getDirectedSuccNodes(N nodeValue); public abstract DiGraphNode<N, E> createDirectedGraphNode(N nodeValue); public abstract DiGraphNode<N, E> getDirectedGraphNode(N nodeValue); public abstract List<DiGraphEdge<N, E>> getDirectedGraphEdges(N n1, N n2); /** * Disconnects all edges from n1 to n2. * * @param n1 Source node. * @param n2 Destination node. */ public abstract void disconnectInDirection(N n1, N n2); /** * Checks whether two nodes in the graph are connected via a directed edge. * * @param n1 Node 1. * @param n2 Node 2. * @return <code>true</code> if the graph contains edge from n1 to n2. */ public abstract boolean isConnectedInDirection(N n1, N n2); /** * Checks whether two nodes in the graph are connected via a directed edge * with the given value. * * @param n1 Node 1. * @param edgeValue edge value tag * @param n2 Node 2. * @return <code>true</code> if the edge exists. */ public abstract boolean isConnectedInDirection(N n1, E edgeValue, N n2); @Override public boolean isConnected(N n1, N n2)

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> { return isConnectedInDirection(n1, n2) || isConnectedInDirection(n2, n1); } @Override public boolean isConnected(N n1, E e, N n2) { return isConnectedInDirection(n1, e, n2) || isConnectedInDirection(n2, e, n1); } /** * A generic directed graph node. * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public static interface DiGraphNode<N, E> extends GraphNode<N, E> { public List<DiGraphEdge<N, E>> getOutEdges(); public List<DiGraphEdge<N, E>> getInEdges(); } /** * A generic directed graph edge. * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public static interface DiGraphEdge<N, E> extends GraphEdge<N, E> { public DiGraphNode<N, E> getSource(); public DiGraphNode<N, E> getDestination(); public void setSource(DiGraphNode<N, E> node); public void setDestination(DiGraphNode<N, E> node); } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.javascript.jscomp.CheckLevel; import java.io.Serializable; import java.util.*; import java.util.Map; import java.util.TreeSet; /** * WarningsGuard that represents just a chain of other guards. For example we * could have following chain * 1) all warnings outside of /foo/ should be suppressed * 2) errors with key JSC_BAR should be marked as warning * 3) the rest should be reported as error * * This class is designed for such behavior. * * @author anatol@google.com (Anatol Pomazau) */ public class ComposeWarningsGuard extends WarningsGuard { private static final long serialVersionUID = 1L; // The order that the guards were added in. private final Map<WarningsGuard, Integer> orderOfAddition = Maps.newHashMap(); private int numberOfAdds = 0; private final Comparator<WarningsGuard> guardComparator = new GuardComparator(orderOfAddition); private boolean demoteErrors = false; private static class GuardComparator implements Comparator<WarningsGuard>, Serializable { private static final long serialVersionUID = 1L; private final Map<WarningsGuard, Integer> orderOfAddition; private GuardComparator(Map<WarningsGuard, Integer> orderOfAddition) { this.orderOfAddition = orderOfAddition; } @Override public int compare(WarningsGuard a, WarningsGuard b) { int priorityDiff = a.getPriority() - b.getPriority(); if (priorityDiff != 0) { return priorityDiff; } // If the warnings guards have the same priority, the one that // was added last wins. return orderOfAddition.get(b).intValue() - orderOfAddition.get(a).intValue(); } } // The order that the guards are applied in. private final TreeSet<WarningsGuard> guards = new TreeSet<WarningsGuard>(guardComparator); public ComposeWarningsGuard(List<WarningsGuard> guards) { addGuards(guards); } public ComposeWarningsGuard(WarningsGuard... guards) { this(Lists.newArrayList(guards)); } void addGuard(WarningsGuard guard) { if (guard instanceof ComposeWarningsGuard) { ComposeWarningsGuard composeGuard = (ComposeWarningsGuard) guard; if (composeGuard.demoteErrors) { this.demoteErrors = composeGuard.demoteErrors; } // Reverse the guards, so that they have the same order in the result.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> addGuards(Lists.newArrayList(composeGuard.guards.descendingSet())); } else { numberOfAdds++; orderOfAddition.put(guard, numberOfAdds); guards.remove(guard); guards.add(guard); } } private void addGuards(Iterable<WarningsGuard> guards) { for (WarningsGuard guard : guards) { addGuard(guard); } } @Override public CheckLevel level(JSError error) { for (WarningsGuard guard : guards) { CheckLevel newLevel = guard.level(error); if (newLevel != null) { if (demoteErrors && newLevel == CheckLevel.ERROR) { return CheckLevel.WARNING; } return newLevel; } } return null; } @Override public boolean disables(DiagnosticGroup group) { nextSingleton: for (DiagnosticType type : group.getTypes()) { DiagnosticGroup singleton = DiagnosticGroup.forType(type); for (WarningsGuard guard : guards) { if (guard.disables(singleton)) { continue nextSingleton; } else if (guard.enables(singleton)) { return false; } } return false; } return true; } /** * Determines whether this guard will "elevate" the status of any disabled * diagnostic type in the group to a warning or an error. */ @Override public boolean enables(DiagnosticGroup group) { for (WarningsGuard guard : guards) { if (guard.enables(group)) { return true; } else if (guard.disables(group)) { return false; } } return false; } List<WarningsGuard> getGuards() { return Collections.unmodifiableList(Lists.newArrayList(guards)); } /** * Make a warnings guard that's the same as this one but with * all escalating guards turned down. */ ComposeWarningsGuard makeEmergencyFailSafeGuard() { ComposeWarningsGuard safeGuard = new ComposeWarningsGuard(); safeGuard.demoteErrors = true; for (WarningsGuard guard : guards.descendingSet()) { safeGuard.addGuard(guard); } return safeGuard; } @Override public String toString() { return Joiner.on(", ").join(guards); } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.javascript.jscomp.CodeChangeHandler.RecentChange; import com.google.javascript.jscomp.CompilerOptions.LanguageMode; import com.google.javascript.jscomp.type.ReverseAbstractInterpreter; import com.google.javascript.jscomp.type.SemanticReverseAbstractInterpreter; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.testing.BaseJSTypeTestCase; import junit.framework.TestCase; import java.io.IOException; import java.util.List; /** * <p>Base class for testing JS compiler classes that change * the node tree of a compiled JS input.</p> * * <p>Pulls in shared functionality from different test cases. Also supports * node tree comparison for input and output (instead of string comparison), * which makes it easier to write tests b/c you don't have to get the syntax * exactly correct to the spacing.</p> * */ public abstract class CompilerTestCase extends TestCase { /** Externs for the test */ private final List<SourceFile> externsInputs; /** Whether to compare input and output as trees instead of strings */ private final boolean compareAsTree; /** Whether to parse type info from JSDoc comments */ protected boolean parseTypeInfo; /** Whether we check warnings without source information. */ private boolean allowSourcelessWarnings = false; /** True iff closure pass runs before pass being tested. */ private boolean closurePassEnabled = false; /** True iff type checking pass runs before pass being tested. */ private boolean typeCheckEnabled = false; /** Error level reported by type checker. */ private CheckLevel typeCheckLevel; /** Whether the Normalize pass runs before pass being tested. */ private boolean normalizeEnabled = false; /** Whether the expected JS strings should be normalized. */ private boolean normalizeExpected = false; /** Whether to check that all line number information is preserved. */ private boolean checkLineNumbers = true; /** * An expected symbol table error. Only useful for testing the * symbol table error-handling. */ private DiagnosticType expectedSymbolTableError = null; /** * Whether the MarkNoSideEffectsCalls pass runs before the pass being tested */ private boolean markNoSideEffects = false; /** The most recently used Compiler instance. */ private Compiler lastCompiler; /** * Whether to acceptES5 source. */ private boolean acceptES5 = true; /** * Whether externs changes should

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> * @param warning Expected warning, or null if no warning is expected * @param description The content of the error expected */ public void test(String js, String expected, DiagnosticType error, DiagnosticType warning, String description) { test(externsInputs, js, expected, error, warning, description); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param js Input * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected */ public void test(String js, String expected, DiagnosticType error, DiagnosticType warning) { test(externsInputs, js, expected, error, warning, null); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param externs Externs input * @param js Input * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected */ public void test(String externs, String js, String expected, DiagnosticType error, DiagnosticType warning) { test(externs, js, expected, error, warning, null); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param externs Externs input * @param js Input * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, * or null if no warning is expected or if the warning's description * should not be examined */ public void test(String externs, String js, String expected, DiagnosticType error, DiagnosticType warning, String description) { List<SourceFile> externsInputs = ImmutableList.of( SourceFile.fromCode("externs", externs)); test(externsInputs, js, expected, error, warning, description); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param externs Externs inputs * @param js Input * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, * or null if no warning is expected or if the warning's description * should not be examined */ public void test(List<SourceFile> externs, String js, String expected, DiagnosticType error, Diagnostic

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Type warning, String description) { Compiler compiler = createCompiler(); lastCompiler = compiler; CompilerOptions options = getOptions(); if (this.acceptES5) { options.setLanguageIn(LanguageMode.ECMASCRIPT5); } // Note that in this context, turning on the checkTypes option won't // actually cause the type check to run. options.checkTypes = parseTypeInfo; compiler.init(externs, ImmutableList.of( SourceFile.fromCode(filename, js)), options); BaseJSTypeTestCase.addNativeProperties(compiler.getTypeRegistry()); test(compiler, new String[] { expected }, error, warning, description); } /** * Verifies that the compiler pass's JS output matches the expected output. * * @param js Inputs * @param expected Expected JS output */ public void test(String[] js, String[] expected) { test(js, expected, null); } /** * Verifies that the compiler pass's JS output matches the expected output, * or that an expected error is encountered. * * @param js Inputs * @param expected Expected JS output * @param error Expected error, or null if no error is expected */ public void test(String[] js, String[] expected, DiagnosticType error) { test(js, expected, error, null); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param js Inputs * @param expected Expected JS output * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected */ public void test(String[] js, String[] expected, DiagnosticType error, DiagnosticType warning) { test(js, expected, error, warning, null); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param js Inputs * @param expected Expected JS output * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, * or null if no warning is expected or if the warning's description * should not be examined */ public void test(String[] js, String[] expected, DiagnosticType error, DiagnosticType warning, String description) { Compiler compiler = createCompiler(); lastCompiler = compiler; List<SourceFile> inputs = Lists.newArrayList(); for (int i = 0; i < js.length; i++) { inputs.add(SourceFile.fromCode("input" + i, js[i])); } compiler.init(externsInputs, inputs, getOptions()); test(compiler, expected, error, warning, description); } /** * Verifies that the compiler pass's JS output matches the expected output. * * @param modules Module inputs * @param expected Expected JS outputs (one per module) */ public void test(JSModule[] modules, String[] expected) { test(modules, expected, null); } /** * Verifies that the compiler pass's JS output matches the expected output, *

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> * Verifies that the compiler pass's JS output is the same as its input * and (optionally) that an expected warning and description is issued. * * @param externs Externs input * @param js Input and output * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, * or null if no warning is expected or if the warning's description * should not be examined */ public void testSame(String externs, String js, DiagnosticType warning, String description) { List<SourceFile> externsInputs = ImmutableList.of( SourceFile.fromCode("externs", externs)); test(externsInputs, js, js, null, warning, description); } /** * Verifies that the compiler pass's JS output is the same as its input. * * @param js Inputs and outputs */ public void testSame(String[] js) { test(js, js); } /** * Verifies that the compiler pass's JS output is the same as its input, * and emits the given error. * * @param js Inputs and outputs * @param error Expected error, or null if no error is expected */ public void testSame(String[] js, DiagnosticType error) { test(js, js, error); } /** * Verifies that the compiler pass's JS output is the same as its input, * and emits the given error and warning. * * @param js Inputs and outputs * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected */ public void testSame( String[] js, DiagnosticType error, DiagnosticType warning) { test(js, js, error, warning); } /** * Verifies that the compiler pass's JS output is the same as the input. * * @param modules Module inputs */ public void testSame(JSModule[] modules) { testSame(modules, null); } /** * Verifies that the compiler pass's JS output is the same as the input. * * @param modules Module inputs * @param warning A warning, or null for no expected warning. */ public void testSame(JSModule[] modules, DiagnosticType warning) { try { String[] expected = new String[modules.length]; for (int i = 0; i < modules.length; i++) { expected[i] = ""; for (CompilerInput input : modules[i].getInputs()) { expected[i] += input.getSourceFile().getCode(); } } test(modules, expected, null, warning); } catch (IOException e) { throw new RuntimeException(e); } } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param compiler A compiler that has been initialized via * {@link Compiler#init} * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected */ protected void test(Compiler compiler, String[] expected, DiagnosticType error, DiagnosticType warning) { test(compiler, expected, error, warning,

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> null); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param compiler A compiler that has been initialized via * {@link Compiler#init} * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, * or null if no warning is expected or if the warning's description * should not be examined */ private void test(Compiler compiler, String[] expected, DiagnosticType error, DiagnosticType warning, String description) { RecentChange recentChange = new RecentChange(); compiler.addChangeHandler(recentChange); Node root = compiler.parseInputs(); assertTrue("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root != null); if (astValidationEnabled) { (new AstValidator()).validateRoot(root); } Node externsRoot = root.getFirstChild(); Node mainRoot = root.getLastChild(); // Save the tree for later comparison. Node rootClone = root.cloneTree(); Node externsRootClone = rootClone.getFirstChild(); Node mainRootClone = rootClone.getLastChild(); int numRepetitions = getNumRepetitions(); ErrorManager[] errorManagers = new ErrorManager[numRepetitions]; int aggregateWarningCount = 0; List<JSError> aggregateWarnings = Lists.newArrayList(); boolean hasCodeChanged = false; assertFalse("Code should not change before processing", recentChange.hasCodeChanged()); for (int i = 0; i < numRepetitions; ++i) { if (compiler.getErrorCount() == 0) { errorManagers[i] = new BlackHoleErrorManager(compiler); // Only run process closure primitives once, if asked. if (closurePassEnabled && i == 0) { recentChange.reset(); new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR) .process(null, mainRoot); hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); } // Only run the type checking pass once, if asked. // Running it twice can cause unpredictable behavior because duplicate // objects for the same type are created, and the type system // uses reference equality to compare many types. if (typeCheckEnabled && i == 0) { TypeCheck check = createTypeCheck(compiler, typeCheckLevel); check.processForTesting(externsRoot, mainRoot); } // Only run the normalize pass once, if asked. if (normalizeEnabled && i == 0) { normalizeActualCode(compiler, externsRoot, mainRoot); } if (markNoSideEffects && i == 0) { MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler); mark.process(externsRoot, mainRoot); } recentChange.reset(); getProcessor(compiler).process(externsRoot, mainRoot); if (astValidationEnabled) { (new AstValidator()).validateRoot(root); } if (checkLineNumbers) { (new LineNumberCheck(compiler)).process(externsRoot, mainRoot);

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } else if (expected != null) { assertEquals( Joiner.on("").join(expected), compiler.toSource(mainRoot)); } // Verify normalization is not invalidated. Node normalizeCheckRootClone = root.cloneTree(); Node normalizeCheckExternsRootClone = root.getFirstChild(); Node normalizeCheckMainRootClone = root.getLastChild(); new PrepareAst(compiler).process( normalizeCheckExternsRootClone, normalizeCheckMainRootClone); String explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull("Node structure normalization invalidated.\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); // TODO(johnlenz): enable this for most test cases. // Currently, this invalidates test for while-loops, for-loop // initializers, and other naming. However, a set of code // (FoldConstants, etc) runs before the Normalize pass, so this can't be // force on everywhere. if (normalizeEnabled) { new Normalize(compiler, true).process( normalizeCheckExternsRootClone, normalizeCheckMainRootClone); explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull("Normalization invalidated.\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } } else { String errors = ""; for (JSError actualError : compiler.getErrors()) { errors += actualError.description + "\n"; } assertEquals("There should be one error. " + errors, 1, compiler.getErrorCount()); assertEquals(errors, error, compiler.getErrors()[0].getType()); if (warning != null) { String warnings = ""; for (JSError actualError : compiler.getWarnings()) { warnings += actualError.description + "\n"; } assertEquals("There should be one warning. " + warnings, 1, compiler.getWarningCount()); assertEquals(warnings, warning, compiler.getWarnings()[0].getType()); } } } private void normalizeActualCode( Compiler compiler, Node externsRoot, Node mainRoot) { Normalize normalize = new Normalize(compiler, false); normalize.process(externsRoot, mainRoot); } /** * Parses expected JS inputs and returns the root of the parse tree. */ protected Node parseExpectedJs(String[] expected) { Compiler compiler = createCompiler(); List<SourceFile> inputs = Lists.newArrayList(); for (int i = 0; i < expected.length; i++) { inputs.add(SourceFile.fromCode("expected" + i, expected[i])); } compiler.init(externsInputs, inputs, getOptions()); Node root = compiler.parseInputs(); assertTrue("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root != null); Node externsRoot = root.getFirstChild(); Node mainRoot = externsRoot.getNext(); // Only run the normalize pass, if asked. if (normalizeEnabled && normalizeExpected && !compiler.hasErrors()) { Normalize normalize = new Normalize

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>(compiler, false); normalize.process(externsRoot, mainRoot); } return mainRoot; } protected Node parseExpectedJs(String expected) { return parseExpectedJs(new String[] {expected}); } /** * Generates a list of modules from a list of inputs, such that each module * depends on the module before it. */ static JSModule[] createModuleChain(String... inputs) { JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[i - 1]); } return modules; } /** * Generates a list of modules from a list of inputs, such that each module * depends on the first module. */ static JSModule[] createModuleStar(String... inputs) { JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[0]); } return modules; } /** * Generates a list of modules from a list of inputs, such that modules * form a bush formation. In a bush formation, module 2 depends * on module 1, and all other modules depend on module 2. */ static JSModule[] createModuleBush(String ... inputs) { Preconditions.checkState(inputs.length > 2); JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[i == 1 ? 0 : 1]); } return modules; } /** * Generates a list of modules from a list of inputs, such that modules * form a tree formation. In a tree formation, module N depends on * module `floor(N/2)`, So the modules form a balanced binary tree. */ static JSModule[] createModuleTree(String ... inputs) { JSModule[] modules = createModules(inputs); for (int i = 1; i < modules.length; i++) { modules[i].addDependency(modules[(i - 1) / 2]); } return modules; } /** * Generates a list of modules from a list of inputs. Does not generate any * dependencies between the modules. */ static JSModule[] createModules(String... inputs) { JSModule[] modules = new JSModule[inputs.length]; for (int i = 0; i < inputs.length; i++) { JSModule module = modules[i] = new JSModule("m" + i); module.add(SourceFile.fromCode("i" + i, inputs[i])); } return modules; } private static class BlackHoleErrorManager extends BasicErrorManager { private BlackHoleErrorManager(Compiler compiler) { compiler.setErrorManager(this); } @Override public void println(CheckLevel level, JSError error) {} @Override public void printSummary() {} } Compiler createCompiler() { Compiler compiler = new Compiler(); return compiler; } protected void setExpectedSymbolTableError(DiagnosticType type) { this.expectedSymbolTableError = type; } /** Finds the first matching qualified name node in post-traversal order. */ protected final Node findQualifiedNameNode(final String name, Node root) { final List<Node> matches = Lists.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2007 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Sets; import com.google.javascript.jscomp.CheckLevel; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.SortedSet; /** * <p>A basic error manager that sorts all errors and warnings reported to it to * generate a sorted report when the {@link #generateReport()} method * is called.</p> * * <p>This error manager does not produce any output, but subclasses can * override the {@link #println(CheckLevel, JSError)} method to generate custom * output.</p> * */ public abstract class BasicErrorManager implements ErrorManager { private final SortedSet<ErrorWithLevel> messages = Sets.newTreeSet(new LeveledJSErrorComparator()); private int errorCount = 0; private int warningCount = 0; private double typedPercent = 0.0; @Override public void report(CheckLevel level, JSError error) { if (messages.add(new ErrorWithLevel(error, level))) { if (level == CheckLevel.ERROR) { errorCount++; } else if (level == CheckLevel.WARNING) { warningCount++; } } } @Override public void generateReport() { for (ErrorWithLevel message : messages) { println(message.level, message.error); } printSummary(); } /** * Print a message with a trailing new line. This method is called by the * {@link #generateReport()} method when generating messages. */ public abstract void println(CheckLevel level, JSError error); /** * Print the summary of the compilation - number of errors and warnings. */ protected abstract void printSummary(); @Override public int getErrorCount() { return errorCount; } @Override public int getWarningCount() { return warningCount; } @Override public JSError[] getErrors() { return toArray(CheckLevel.ERROR); } @Override public JSError[] getWarnings() { return toArray(CheckLevel.WARNING); } @Override public void setTypedPercent(double typedPercent) { this.typedPercent = typedPercent; } @Override public double getTypedPercent() { return typedPercent; } private JSError[] toArray(CheckLevel level) { List<JSError> errors = new ArrayList<JSError>(messages.size()); for (ErrorWithLevel p : messages) { if (p.level == level) { errors.add(p.error); } } return errors.toArray(new JSError[errors.size()]); } /** *

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Supplier; import com.google.javascript.jscomp.ReferenceCollectingCallback.ReferenceCollection; import com.google.javascript.jscomp.Scope.Var; import com.google.javascript.jscomp.parsing.Config; import com.google.javascript.jscomp.type.ReverseAbstractInterpreter; import com.google.javascript.rhino.InputId; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.head.ErrorReporter; import com.google.javascript.rhino.jstype.JSTypeRegistry; import java.util.List; import java.util.Map; /** * An abstract compiler, to help remove the circular dependency of * passes on JSCompiler. * * This is an abstract class, so that we can make the methods package-private. * * @author nicksantos@google.com (Nick Santos) */ public abstract class AbstractCompiler implements SourceExcerptProvider { static final DiagnosticType READ_ERROR = DiagnosticType.error( "JSC_READ_ERROR", "Cannot read: {0}"); private LifeCycleStage stage = LifeCycleStage.RAW; // TODO(nicksantos): Decide if all of these are really necessary. // Many of them are just accessors that should be passed to the // CompilerPass's constructor. /** * Looks up an input (possibly an externs input) by input id. * May return null. */ public abstract CompilerInput getInput(InputId inputId); /** * Looks up a source file by name. May return null. */ abstract SourceFile getSourceFileByName(String sourceName); /** * Creates a new externs file. * @param name A name for the new externs file. * @throws IllegalArgumentException If the name of the externs file conflicts * with a pre-existing externs file. */ abstract CompilerInput newExternInput(String name); /** * Gets the module graph. May return null if there aren't at least two * modules. */ abstract JSModuleGraph getModuleGraph(); /** * Gets the inputs in the order in which they are being processed. * Only for use by {@code AbstractCompilerRunner}. */ abstract List<CompilerInput> getInputsInOrder(); /** * Gets a central registry of type information from the compiled JS. */ public abstract JSTypeRegistry getTypeRegistry(); /** * Gets a memoized scope creator with type information. */ abstract ScopeCreator getTypedScopeCreator(); /** * Gets the top scope. */ public abstract Scope getTopScope(); /** * Report an error or warning. */ public abstract void report(JSError

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> } private final SourceMapGenerator generator; private List<LocationMapping> prefixMappings = Collections.emptyList(); private final Map<String, String> sourceLocationFixupCache = Maps.newHashMap(); private SourceMap(SourceMapGenerator generator) { this.generator = generator; } public void addMapping( Node node, FilePosition outputStartPosition, FilePosition outputEndPosition) { String sourceFile = node.getSourceFileName(); // If the node does not have an associated source file or // its line number is -1, then the node does not have sufficient // information for a mapping to be useful. if (sourceFile == null || node.getLineno() < 0) { return; } sourceFile = fixupSourceLocation(sourceFile); String originalName = (String) node.getProp(Node.ORIGINALNAME_PROP); // Strangely, Rhino source lines are one based but columns are // zero based. // We don't change this for the v1 or v2 source maps but for // v3 we make them both 0 based. int lineBaseOffset = 1; if (generator instanceof SourceMapGeneratorV1 || generator instanceof SourceMapGeneratorV2) { lineBaseOffset = 0; } generator.addMapping( sourceFile, originalName, new FilePosition(node.getLineno() - lineBaseOffset, node.getCharno()), outputStartPosition, outputEndPosition); } /** * @param sourceFile The source file location to fixup. * @return a remapped source file. */ private String fixupSourceLocation(String sourceFile) { if (prefixMappings.isEmpty()) { return sourceFile; } String fixed = sourceLocationFixupCache.get(sourceFile); if (fixed != null) { return fixed; } // Replace the first prefix found with its replacement for (LocationMapping mapping : prefixMappings) { if (sourceFile.startsWith(mapping.prefix)) { fixed = mapping.replacement + sourceFile.substring( mapping.prefix.length()); break; } } // If none of the mappings match then use the original file path. if (fixed == null) { fixed = sourceFile; } sourceLocationFixupCache.put(sourceFile, fixed); return fixed; } public void appendTo(Appendable out, String name) throws IOException { generator.appendTo(out, name); } public void reset() { generator.reset(); sourceLocationFixupCache.clear(); } public void setStartingPosition(int offsetLine, int offsetIndex) { generator.setStartingPosition(offsetLine, offsetIndex); } public void setWrapperPrefix(String prefix) { generator.setWrapperPrefix(prefix); } public void validate(boolean validate) { generator.validate(validate); } /** * @param sourceMapLocationMappings */ public void setPrefixMappings(List<LocationMapping> sourceMapLocationMappings) { this.prefixMappings = sourceMapLocationMappings; } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.graph; /** * A generic node. * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public interface GraphNode<N, E> extends Annotatable { /** * Retrieves the node's value. * * @return The value. */ N getValue(); }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.javascript.jscomp.deps.SortedDependencies; import com.google.javascript.jscomp.deps.SortedDependencies.CircularDependencyException; import com.google.javascript.jscomp.deps.SortedDependencies.MissingProvideException; import com.google.javascript.jscomp.graph.LinkedDirectedGraph; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * A {@link JSModule} dependency graph that assigns a depth to each module and * can answer depth-related queries about them. For the purposes of this class, * a module's depth is defined as the number of hops in the longest path from * the module to a module with no dependencies. * */ public class JSModuleGraph { private List<JSModule> modules; /** * Lists of modules at each depth. <code>modulesByDepth.get(3)</code> is a * list of the modules at depth 3, for example. */ private List<List<JSModule>> modulesByDepth; /** * dependencyMap is a cache of dependencies that makes the dependsOn * function faster. Each map entry associates a starting * JSModule with the set of JSModules that are transitively dependent on the * starting module. * * If the cache returns null, then the entry hasn't been filled in for that * module. * * dependencyMap should be filled from leaf to root so that * getTransitiveDepsDeepestFirst can use its results directly. */ private Map<JSModule, Set<JSModule>> dependencyMap = Maps.newHashMap(); /** * Creates a module graph from a list of modules in dependency order. */ public JSModuleGraph(JSModule[] modulesInDepOrder) { this(ImmutableList.copyOf(modulesInDepOrder)); } /** * Creates a module graph from a list of modules in dependency order. */ public JSModuleGraph(List<JSModule> modulesInDep

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Order) { Preconditions.checkState( modulesInDepOrder.size() == Sets.newHashSet(modulesInDepOrder).size(), "Found duplicate modules"); modules = ImmutableList.copyOf(modulesInDepOrder); modulesByDepth = Lists.newArrayList(); for (JSModule module : modulesInDepOrder) { int depth = 0; for (JSModule dep : module.getDependencies()) { int depDepth = dep.getDepth(); if (depDepth < 0) { throw new ModuleDependenceException(String.format( "Modules not in dependency order: %s preceded %s", module.getName(), dep.getName()), module, dep); } depth = Math.max(depth, depDepth + 1); } module.setDepth(depth); if (depth == modulesByDepth.size()) { modulesByDepth.add(new ArrayList<JSModule>()); } modulesByDepth.get(depth).add(module); } } /** * Gets an iterable over all modules in dependency order. */ Iterable<JSModule> getAllModules() { return modules; } /** * Gets the total number of modules. */ int getModuleCount() { return modules.size(); } /** * Gets the root module. */ JSModule getRootModule() { return Iterables.getOnlyElement(modulesByDepth.get(0)); } /** * Determines whether this module depends on a given module. Note that a * module never depends on itself, as that dependency would be cyclic. */ public boolean dependsOn(JSModule src, JSModule m) { Set<JSModule> deps = dependencyMap.get(src); if (deps == null) { deps = getTransitiveDepsDeepestFirst(src); dependencyMap.put(src, deps); } return deps.contains(m); } /** * Finds the deepest common dependency of two modules, not including the two * modules themselves. * * @param m1 A module in this graph * @param m2 A module in this graph * @return The deepest common dep of {@code m1} and {@code m2}, or null if * they have no common dependencies */ JSModule getDeepestCommonDependency(JSModule m1, JSModule m2) { int m1Depth = m1.getDepth(); int m2Depth = m2.getDepth(); // According our definition of depth, the result must have a strictly // smaller depth than either m1 or m2. for (int depth = Math.min(m1Depth, m2Depth) - 1; depth >= 0; depth--) { List<JSModule> modulesAtDepth = modulesByDepth.get(depth); // Look at the modules at this depth in reverse order, so that we use the // original ordering of the modules to break ties (later meaning deeper). for (int i = modulesAtDepth.size() - 1; i >= 0; i--) { JSModule m = modulesAtDepth.get(i); if (dependsOn(m1, m) && dependsOn(m2, m)) { return m; } } } return null; } /** * Finds the deepest common dependency of two modules, including the * modules themselves. * * @param m1 A module in this graph

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> to reflect the new list. * * If you need more fine-grained dependency management, you should create your * own DependencyOptions and call * {@code manageDependencies(DependencyOptions, List<CompilerInput>)}. * * @param entryPoints The entry points into the program. * Expressed as JS symbols. * @param inputs The original list of sources. Used to ensure that the sort * is stable. * @throws CircularDependencyException if there is a circular dependency * between the provides and requires. * @throws MissingProvideException if an entry point was not provided * by any of the inputs. * @see DependencyOptions for more info on how this works. */ public List<CompilerInput> manageDependencies( List<String> entryPoints, List<CompilerInput> inputs) throws CircularDependencyException, MissingProvideException { DependencyOptions depOptions = new DependencyOptions(); depOptions.setDependencySorting(true); depOptions.setDependencyPruning(true); depOptions.setEntryPoints(entryPoints); return manageDependencies(depOptions, inputs); } /** * Apply the dependency options to the list of sources, returning a new * source list re-ordering and dropping files as necessary. * This module graph will be updated to reflect the new list. * * @param inputs The original list of sources. Used to ensure that the sort * is stable. * @throws CircularDependencyException if there is a circular dependency * between the provides and requires. * @throws MissingProvideException if an entry point was not provided * by any of the inputs. * @see DependencyOptions for more info on how this works. */ public List<CompilerInput> manageDependencies( DependencyOptions depOptions, List<CompilerInput> inputs) throws CircularDependencyException, MissingProvideException { SortedDependencies<CompilerInput> sorter = new SortedDependencies<CompilerInput>(inputs); Set<CompilerInput> entryPointInputs = Sets.newLinkedHashSet(); if (depOptions.shouldPruneDependencies()) { if (!depOptions.shouldDropMoochers()) { entryPointInputs.addAll(sorter.getInputsWithoutProvides()); } for (String entryPoint : depOptions.getEntryPoints()) { entryPointInputs.add(sorter.getInputProviding(entryPoint)); } CompilerInput baseJs = sorter.maybeGetInputProviding("goog"); if (baseJs != null) { entryPointInputs.add(baseJs); } } else { entryPointInputs.addAll(inputs); } // The order of inputs, sorted independently of modules. List<CompilerInput> absoluteOrder = sorter.getDependenciesOf(inputs, depOptions.shouldSortDependencies()); // Figure out which sources *must* be in each module. ListMultimap<JSModule, CompilerInput> entryPointInputsPerModule = LinkedListMultimap.create(); for (CompilerInput input : entryPointInputs) { JSModule module = input.getModule(); Preconditions.checkNotNull(module); entryPointInputsPerModule.put(module, input); } // Clear the modules of their inputs. This also nulls out // the input's reference to its module. for (JSModule module : getAllModules()) { module.removeAll(); } // Figure out which sources *must* be in each module, or in one // of that module's dependencies. for (JSModule module : entryPointInputsPerModule.keySet()) {

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> List<CompilerInput> transitiveClosure = sorter.getDependenciesOf( entryPointInputsPerModule.get(module), depOptions.shouldSortDependencies()); for (CompilerInput input : transitiveClosure) { JSModule oldModule = input.getModule(); if (oldModule == null) { input.setModule(module); } else { input.setModule(null); input.setModule( getDeepestCommonDependencyInclusive(oldModule, module)); } } } // All the inputs are pointing to the modules that own them. Yeah! // Update the modules to reflect this. for (CompilerInput input : absoluteOrder) { JSModule module = input.getModule(); if (module != null) { module.add(input); } } // Now, generate the sorted result. List<CompilerInput> result = Lists.newArrayList(); for (JSModule module : getAllModules()) { result.addAll(module.getInputs()); } return result; } LinkedDirectedGraph<JSModule, String> toGraphvizGraph() { LinkedDirectedGraph<JSModule, String> graphViz = LinkedDirectedGraph.create(); for (JSModule module : getAllModules()) { graphViz.createNode(module); for (JSModule dep : module.getDependencies()) { graphViz.createNode(dep); graphViz.connect(module, "->", dep); } } return graphViz; } /** * A module depth comparator that considers a deeper module to be "less than" * a shallower module. Uses module names to consistently break ties. */ private class InverseDepthComparator implements Comparator<JSModule> { @Override public int compare(JSModule m1, JSModule m2) { return depthCompare(m2, m1); } } private int depthCompare(JSModule m1, JSModule m2) { if (m1 == m2) { return 0; } int d1 = m1.getDepth(); int d2 = m2.getDepth(); return d1 < d2 ? -1 : d2 == d1 ? m1.getName().compareTo(m2.getName()) : 1; } /* * Exception class for declaring when the modules being fed into a * JSModuleGraph as input aren't in dependence order, and so can't be * processed for caching of various dependency-related queries. */ protected static class ModuleDependenceException extends IllegalArgumentException { private static final long serialVersionUID = 1; private final JSModule module; private final JSModule dependentModule; protected ModuleDependenceException(String message, JSModule module, JSModule dependentModule) { super(message); this.module = module; this.dependentModule = dependentModule; } public JSModule getModule() { return module; } public JSModule getDependentModule() { return dependentModule; } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2005 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.javascript.jscomp.deps.DependencyInfo; import com.google.javascript.jscomp.deps.SortedDependencies; import com.google.javascript.jscomp.deps.SortedDependencies.CircularDependencyException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; /** * A JavaScript module has a unique name, consists of a list of compiler inputs, * and can depend on other modules. * */ public class JSModule implements DependencyInfo, Serializable { private static final long serialVersionUID = 1; static final DiagnosticType CIRCULAR_DEPENDENCY_ERROR = DiagnosticType.error("JSC_CIRCULAR_DEP", "Circular dependency detected: {0}"); /** Module name */ private final String name; /** Source code inputs */ private final List<CompilerInput> inputs = new ArrayList<CompilerInput>(); /** Modules that this module depends on */ private final List<JSModule> deps = new ArrayList<JSModule>(); private int depth; /** * Creates an instance. * * @param name A unique name for the module */ public JSModule(String name) { this.name = name; this.depth = -1; } /** Gets the module name. */ @Override public String getName() { return name; } @Override public List<String> getProvides() { return ImmutableList.<String>of(name); } @Override public List<String> getRequires() { ImmutableList.Builder<String> builder = ImmutableList.builder(); for (JSModule m : deps) { builder.add(m.getName()); } return builder.build(); } @Override public String getPathRelativeToClosureBase() { throw new UnsupportedOperationException(); } /** Adds a source file input to this module. */ public void add(SourceFile file) { add(new CompilerInput(file)); } /** Adds a source file input to this module. */ public void addFirst(SourceFile file) { addFirst(new CompilerInput(file)); } /** Adds a source code input to this module. */ public void add(CompilerInput input) { inputs.add(input); input.setModule(this); } /** * Adds a source code input to this module. Call only if the input might * already be associated with a module.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> Otherwise, use * add(CompilerInput input). */ void addAndOverrideModule(CompilerInput input) { inputs.add(input); input.overrideModule(this); } /** Adds a source code input to this module. */ public void addFirst(CompilerInput input) { inputs.add(0, input); input.setModule(this); } /** Adds a source code input to this module directly after other. */ public void addAfter(CompilerInput input, CompilerInput other) { Preconditions.checkState(inputs.contains(other)); inputs.add(inputs.indexOf(other), input); input.setModule(this); } /** Adds a dependency on another module. */ public void addDependency(JSModule dep) { Preconditions.checkNotNull(dep); Preconditions.checkState(dep != this); deps.add(dep); } /** Removes an input from this module. */ public void remove(CompilerInput input) { input.setModule(null); inputs.remove(input); } /** Removes all of the inputs from this module. */ public void removeAll() { for (CompilerInput input : inputs) { input.setModule(null); } inputs.clear(); } /** * Gets the list of modules that this module depends on. * * @return A list that may be empty but not null */ public List<JSModule> getDependencies() { return deps; } /** * Gets the names of the modules that this module depends on, * sorted alphabetically. */ List<String> getSortedDependencyNames() { List<String> names = Lists.newArrayList(); for (JSModule module : getDependencies()) { names.add(module.getName()); } Collections.sort(names); return names; } /** * Returns the transitive closure of dependencies starting from the * dependencies of this module. */ public Set<JSModule> getAllDependencies() { Set<JSModule> allDeps = Sets.newHashSet(deps); List<JSModule> workList = Lists.newArrayList(deps); while (workList.size() > 0) { JSModule module = workList.remove(workList.size() - 1); for (JSModule dep : module.getDependencies()) { if (allDeps.add(dep)) { workList.add(dep); } } } return allDeps; } /** Returns this module and all of its dependencies in one list. */ public Set<JSModule> getThisAndAllDependencies() { Set<JSModule> deps = getAllDependencies(); deps.add(this); return deps; } /** * Gets this module's list of source code inputs. * * @return A list that may be empty but not null */ public List<CompilerInput> getInputs() { return inputs; } /** Returns the input with the given name or null if none. */ public CompilerInput getByName(String name) { for (CompilerInput input : inputs) { if (name.equals(input.getName())) { return input; } } return null; } /** * Removes any input with the given name. Returns whether any were removed. */ public boolean removeByName(String name) { boolean found = false; Iterator<CompilerInput> iter = inputs.iterator(); while (iter.hasNext()) { CompilerInput file =

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> iter.next(); if (name.equals(file.getName())) { iter.remove(); file.setModule(null); found = true; } } return found; } /** Returns the module name (primarily for debugging). */ @Override public String toString() { return name; } /** * Removes any references to nodes of the AST. This method is needed to * allow the ASTs to be garbage collected if the modules are kept around. */ public void clearAsts() { for (CompilerInput input : inputs) { input.clearAst(); } } /** * Puts the JS files into a topologically sorted order by their dependencies. */ public void sortInputsByDeps(Compiler compiler) { // Set the compiler, so that we can parse requires/provides and report // errors properly. for (CompilerInput input : inputs) { input.setCompiler(compiler); } // Sort the JSModule in this order. try { List<CompilerInput> sortedList = (new SortedDependencies<CompilerInput>( Collections.<CompilerInput>unmodifiableList(inputs))) .getSortedList(); inputs.clear(); inputs.addAll(sortedList); } catch (CircularDependencyException e) { compiler.report( JSError.make(CIRCULAR_DEPENDENCY_ERROR, e.getMessage())); } } /** * Returns the given collection of modules in topological order. * * Note that this will return the modules in the same order if they are * already sorted, and in general, will only change the order as necessary to * satisfy the ordering dependencies. This can be important for cases where * the modules do not properly specify all dependencies. */ public static JSModule[] sortJsModules(Collection<JSModule> modules) throws CircularDependencyException { // Sort the JSModule in this order. List<JSModule> sortedList = (new SortedDependencies<JSModule>( Lists.newArrayList(modules))).getSortedList(); return sortedList.toArray(new JSModule[sortedList.size()]); } /** * @param dep the depth to set */ public void setDepth(int dep) { this.depth = dep; } /** * @return the depth */ public int getDepth() { return depth; } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> n, Node parent) { if (parent != null) { switch (parent.getType()) { case Token.DO: case Token.FOR: case Token.TRY: case Token.WHILE: case Token.WITH: // NOTE: TRY has up to 3 child blocks: // TRY // BLOCK // BLOCK // CATCH // BLOCK // Note that there is an explicit CATCH token but no explicit // FINALLY token. For simplicity, we consider each BLOCK // a separate basic BLOCK. return true; case Token.AND: case Token.HOOK: case Token.IF: case Token.OR: // The first child of a conditional is not a boundary, // but all the rest of the children are. return n != parent.getFirstChild(); } } return n.isCase(); } private void addReference(NodeTraversal t, Var v, Reference reference) { // Create collection if none already ReferenceCollection referenceInfo = referenceMap.get(v); if (referenceInfo == null) { referenceInfo = new ReferenceCollection(); referenceMap.put(v, referenceInfo); } // Add this particular reference referenceInfo.add(reference, t, v); } interface ReferenceMap { ReferenceCollection getReferences(Var var); } private static class ReferenceMapWrapper implements ReferenceMap { private final Map<Var, ReferenceCollection> referenceMap; public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) { this.referenceMap = referenceMap; } @Override public ReferenceCollection getReferences(Var var) { return referenceMap.get(var); } } /** * Way for callers to add specific behavior during traversal that * utilizes the built-up reference information. */ interface Behavior { /** * Called after we finish with a scope. */ void afterExitScope(NodeTraversal t, ReferenceMap referenceMap); } static Behavior DO_NOTHING_BEHAVIOR = new Behavior() { @Override public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {} }; /** * A collection of references. Can be subclassed to apply checks or * store additional state when adding. */ static class ReferenceCollection implements Iterable<Reference> { List<Reference> references = Lists.newArrayList(); @Override public Iterator<Reference> iterator() { return references.iterator(); } void add(Reference reference, NodeTraversal t, Var v) { references.add(reference); } /** * Determines if the variable for this reference collection is * "well-defined." A variable is well-defined if we can prove at * compile-time that it's assigned a value before it's used. * * Notice that if this function returns false, this doesn't imply that the * variable is used before it's assigned. It just means that we don't * have enough information to make a definitive judgment. */ protected boolean isWellDefined() { int size = references.size(); if (size == 0) { return false; } // If this is a declaration that does not instantiate the variable, // it's not well-defined. Reference init = getInitializingReference(); if (init == null) { return false; } Preconditions.checkState(references.get(0).isDeclaration()); BasicBlock initBlock = init.getBasicBlock(); for (

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>() != null)) { fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense()); } irNode.setJSDocInfo(fileOverviewInfo); fileOverviewInfo.setAssociatedNode(irNode); } } private Node transformBlock(AstNode node) { Node irNode = transform(node); if (!irNode.isBlock()) { if (irNode.isEmpty()) { irNode.setType(Token.BLOCK); irNode.setWasEmptyNode(true); } else { Node newBlock = newNode(Token.BLOCK, irNode); newBlock.setLineno(irNode.getLineno()); newBlock.setCharno(irNode.getCharno()); maybeSetLengthFrom(newBlock, node); irNode = newBlock; } } return irNode; } /** * Check to see if the given block comment looks like it should be JSDoc. */ private void handleBlockComment(Comment comment) { String value = comment.getValue(); if (value.indexOf("/* @") != -1 || value.indexOf("\n * @") != -1) { errorReporter.warning( SUSPICIOUS_COMMENT_WARNING, sourceName, comment.getLineno(), "", 0); } } /** * @return true if the jsDocParser represents a fileoverview. */ private boolean handlePossibleFileOverviewJsDoc( JsDocInfoParser jsDocParser) { if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) { fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo(); return true; } return false; } private void handlePossibleFileOverviewJsDoc(Comment comment, Node irNode) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode); parsedComments.add(comment); handlePossibleFileOverviewJsDoc(jsDocParser); } private JSDocInfo handleJsDoc(AstNode node, Node irNode) { Comment comment = node.getJsDocNode(); if (comment != null) { JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode); parsedComments.add(comment); if (!handlePossibleFileOverviewJsDoc(jsDocParser)) { return jsDocParser.retrieveAndResetParsedJSDocInfo(); } } return null; } private Node transform(AstNode node) { Node irNode = justTransform(node); JSDocInfo jsDocInfo = handleJsDoc(node, irNode); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, node); return irNode; } private Node transformNameAsString(Name node) { Node irNode = transformDispatcher.processName(node, true); JSDocInfo jsDocInfo = handleJsDoc(node, irNode); if (jsDocInfo != null) { irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, node); return irNode; } private Node transformNumberAsString(NumberLiteral literalNode) { Node irNode = newStringNode(getStringValue(literalNode.getNumber())); JSDocInfo jsDocInfo = handleJsDoc(literalNode, irNode); if (jsDocInfo

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> != null) { irNode.setJSDocInfo(jsDocInfo); } setSourceInfo(irNode, literalNode); return irNode; } private static String getStringValue(double value) { long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) { return Long.toString(longValue); } else { return Double.toString(value); } } private void setSourceInfo(Node irNode, AstNode node) { if (irNode.getLineno() == -1) { // If we didn't already set the line, then set it now. This avoids // cases like ParenthesizedExpression where we just return a previous // node, but don't want the new node to get its parent's line number. int lineno = node.getLineno(); irNode.setLineno(lineno); int charno = position2charno(node.getAbsolutePosition()); irNode.setCharno(charno); maybeSetLengthFrom(irNode, node); } } /** * Creates a JsDocInfoParser and parses the JsDoc string. * * Used both for handling individual JSDoc comments and for handling * file-level JSDoc comments (@fileoverview and @license). * * @param node The JsDoc Comment node to parse. * @param irNode * @return A JsDocInfoParser. Will contain either fileoverview JsDoc, or * normal JsDoc, or no JsDoc (if the method parses to the wrong level). */ private JsDocInfoParser createJsDocInfoParser(Comment node, Node irNode) { String comment = node.getValue(); int lineno = node.getLineno(); int position = node.getAbsolutePosition(); // The JsDocInfoParser expects the comment without the initial '/**'. int numOpeningChars = 3; JsDocInfoParser jsdocParser = new JsDocInfoParser( new JsDocTokenStream(comment.substring(numOpeningChars), lineno, position2charno(position) + numOpeningChars), node, irNode, config, errorReporter); jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder); jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo); jsdocParser.parse(); return jsdocParser; } // Set the length on the node if we're in IDE mode. private void maybeSetLengthFrom(Node node, AstNode source) { if (config.isIdeMode) { node.setLength(source.getLength()); } } private int position2charno(int position) { int lineIndex = sourceString.lastIndexOf('\n', position); if (lineIndex == -1) { return position; } else { // Subtract one for initial position being 0. return position - lineIndex - 1; } } private Node justTransform(AstNode node) { return transformDispatcher.process(node); } private class TransformDispatcher extends TypeSafeDispatcher<Node> { private Node processGeneric( com.google.javascript.rhino.head.Node n) { Node node = newNode(transformTokenType(n.getType())); for (com.google.javascript.rhino.head.Node child : n) { node.addChildToBack(transform((AstNode)child)); } return

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Lineno()); node.setCharno(position2charno(exprNode.getAbsolutePosition())); maybeSetLengthFrom(node, exprNode); return node; } @Override Node processNumberLiteral(NumberLiteral literalNode) { return newNumberNode(literalNode.getNumber()); } @Override Node processObjectLiteral(ObjectLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.OBJECTLIT); for (ObjectProperty el : literalNode.getElements()) { if (config.languageMode == LanguageMode.ECMASCRIPT3) { if (el.isGetter()) { reportGetter(el); continue; } else if (el.isSetter()) { reportSetter(el); continue; } } Node key = transformAsString(el.getLeft()); key.setType(Token.STRING_KEY); Node value = transform(el.getRight()); if (el.isGetter()) { key.setType(Token.GETTER_DEF); Preconditions.checkState(value.isFunction()); if (getFnParamNode(value).hasChildren()) { reportGetterParam(el.getLeft()); } } else if (el.isSetter()) { key.setType(Token.SETTER_DEF); Preconditions.checkState(value.isFunction()); if (!getFnParamNode(value).hasOneChild()) { reportSetterParam(el.getLeft()); } } key.addChildToFront(value); node.addChildToBack(key); } return node; } /** * @param fnNode The function. * @return The Node containing the Function parameters. */ Node getFnParamNode(Node fnNode) { // Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ] Preconditions.checkArgument(fnNode.isFunction()); return fnNode.getFirstChild().getNext(); } @Override Node processObjectProperty(ObjectProperty propertyNode) { return processInfixExpression(propertyNode); } @Override Node processParenthesizedExpression(ParenthesizedExpression exprNode) { Node node = transform(exprNode.getExpression()); node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE); return node; } @Override Node processPropertyGet(PropertyGet getNode) { Node leftChild = transform(getNode.getTarget()); Node newNode = newNode( Token.GETPROP, leftChild, transformAsString(getNode.getProperty())); newNode.setLineno(leftChild.getLineno()); newNode.setCharno(leftChild.getCharno()); maybeSetLengthFrom(newNode, getNode); return newNode; } @Override Node processRegExpLiteral(RegExpLiteral literalNode) { Node literalStringNode = newStringNode(literalNode.getValue()); // assume it's on the same line. literalStringNode.setLineno(literalNode.getLineno()); maybeSetLengthFrom(literalStringNode, literalNode); Node node = newNode(Token.REGEXP, literalStringNode); String flags = literalNode.getFlags(); if (flags != null && !flags.isEmpty()) { Node flagsNode = newStringNode(flags); // Assume the flags are on the same line as the literal node. flagsNode.setLineno(literalNode.getLineno()); maybeSetLengthFrom(flagsNode,

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> literalNode); node.addChildToBack(flagsNode); } return node; } @Override Node processReturnStatement(ReturnStatement statementNode) { Node node = newNode(Token.RETURN); if (statementNode.getReturnValue() != null) { node.addChildToBack(transform(statementNode.getReturnValue())); } return node; } @Override Node processScope(Scope scopeNode) { return processGeneric(scopeNode); } @Override Node processStringLiteral(StringLiteral literalNode) { String value = literalNode.getValue(); Node n = newStringNode(value); if (value.indexOf('\u000B') != -1) { // NOTE(nicksantos): In JavaScript, there are 3 ways to // represent a vertical tab: \v, \x0B, \u000B. // The \v notation was added later, and is not understood // on IE. So we need to preserve it as-is. This is really // obnoxious, because we do not have a good way to represent // how the original string was encoded without making the // representation of strings much more complicated. // // To handle this, we look at the original source test, and // mark the string as \v-encoded or not. If a string is // \v encoded, then all the vertical tabs in that string // will be encoded with a \v. int start = literalNode.getAbsolutePosition(); int end = start + literalNode.getLength(); if (start < sourceString.length() && (sourceString.substring( start, Math.min(sourceString.length(), end)) .indexOf("\\v") != -1)) { n.putBooleanProp(Node.SLASH_V, true); } } return n; } @Override Node processSwitchCase(SwitchCase caseNode) { Node node; if (caseNode.isDefault()) { node = newNode(Token.DEFAULT_CASE); } else { AstNode expr = caseNode.getExpression(); node = newNode(Token.CASE, transform(expr)); } Node block = newNode(Token.BLOCK); block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true); block.setLineno(caseNode.getLineno()); block.setCharno(position2charno(caseNode.getAbsolutePosition())); maybeSetLengthFrom(block, caseNode); if (caseNode.getStatements() != null) { for (AstNode child : caseNode.getStatements()) { block.addChildToBack(transform(child)); } } node.addChildToBack(block); return node; } @Override Node processSwitchStatement(SwitchStatement statementNode) { Node node = newNode(Token.SWITCH, transform(statementNode.getExpression())); for (AstNode child : statementNode.getCases()) { node.addChildToBack(transform(child)); } return node; } @Override Node processThrowStatement(ThrowStatement statementNode) { return newNode(Token.THROW, transform(statementNode.getExpression())); } @Override Node processTryStatement(TryStatement statementNode) { Node node = newNode(Token.TRY, transformBlock(statementNode.getTryBlock())); Node block = newNode(Token.BLOCK); boolean lineSet = false; for (CatchClause cc :

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.deps; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; import java.util.ArrayDeque; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.Set; /** * A sorted list of inputs with dependency information. Uses a stable * topological sort to make sure that an input always comes after its * dependencies. * * Also exposes other information about the inputs, like which inputs * do not provide symbols. * * @author nicksantos@google.com (Nick Santos) */ public class SortedDependencies<INPUT extends DependencyInfo> { private final List<INPUT> inputs; // A topologically sorted list of the inputs. private final List<INPUT> sortedList; // A list of all the inputs that do not have provides. private final List<INPUT> noProvides; private final Map<String, INPUT> provideMap = Maps.newHashMap(); public SortedDependencies(List<INPUT> inputs) throws CircularDependencyException { this.inputs = Lists.newArrayList(inputs); noProvides = Lists.newArrayList(); // Collect all symbols provided in these files. for (INPUT input : inputs) { Collection<String> currentProvides = input.getProvides(); if (currentProvides.isEmpty()) { noProvides.add(input); } for (String provide : currentProvides) { provideMap.put(provide, input); } } // Get the direct dependencies. final Multimap<INPUT, INPUT> deps = HashMultimap.create(); for (INPUT input : inputs) { for (String req : input.getRequires()) { INPUT dep = provideMap.get(req); if (dep != null && dep != input) { deps.put(input, dep); } } } // Sort the inputs by sucking in 0-in-degree nodes until we're done. sortedList = topologicalStableSort(inputs,

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> deps); // The dependency graph of inputs has a cycle iff sortedList is a proper // subset of inputs. Also, it has a cycle iff the subgraph // (inputs - sortedList) has a cycle. It's fairly easy to prove this // by the lemma that a graph has a cycle iff it has a subgraph where // no nodes have out-degree 0. I'll leave the proof of this as an exercise // to the reader. if (sortedList.size() < inputs.size()) { List<INPUT> subGraph = Lists.newArrayList(inputs); subGraph.removeAll(sortedList); throw new CircularDependencyException( cycleToString(findCycle(subGraph, deps))); } } /** * Return the input that gives us the given symbol. * @throws MissingProvideException An exception if there is no * input for this symbol. */ public INPUT getInputProviding(String symbol) throws MissingProvideException { if (provideMap.containsKey(symbol)) { return provideMap.get(symbol); } throw new MissingProvideException(symbol); } /** * Return the input that gives us the given symbol, or null. */ public INPUT maybeGetInputProviding(String symbol) { return provideMap.get(symbol); } /** * Returns the first circular dependency found. Expressed as a list of * items in reverse dependency order (the second element depends on the * first, etc.). */ private List<INPUT> findCycle( List<INPUT> subGraph, Multimap<INPUT, INPUT> deps) { return findCycle(subGraph.get(0), Sets.<INPUT>newHashSet(subGraph), deps, Sets.<INPUT>newHashSet()); } private List<INPUT> findCycle( INPUT current, Set<INPUT> subGraph, Multimap<INPUT, INPUT> deps, Set<INPUT> covered) { if (covered.add(current)) { List<INPUT> cycle = findCycle( findRequireInSubGraphOrFail(current, subGraph), subGraph, deps, covered); // Don't add the input to the list if the cycle has closed already. if (cycle.get(0) != cycle.get(cycle.size() - 1)) { cycle.add(current); } return cycle; } else { // Explicitly use the add() method, to prevent a generics constructor // warning that is dumb. The condition it's protecting is // obscure, and I think people have proposed that it be removed. List<INPUT> cycle = Lists.<INPUT>newArrayList(); cycle.add(current); return cycle; } } private INPUT findRequireInSubGraphOrFail(INPUT input, Set<INPUT> subGraph) { for (String symbol : input.getRequires()) { INPUT candidate = provideMap.get(symbol); if (subGraph.contains(candidate)) { return candidate; } } throw new IllegalStateException("no require found in subgraph"); } /** * @param cycle A cycle in reverse-dependency order. */ private String cycleToString(List<INPUT> cycle) { List<String> symbols = Lists.newArrayList(); for (int i = cycle.size() - 1; i >= 0; i--) { symbols.add(cycle.get(i).getProvides().iterator().next()); } symbols.add(symbols.get(0)); return Joiner.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>on(" -> ").join(symbols); } public List<INPUT> getSortedList() { return Collections.<INPUT>unmodifiableList(sortedList); } /** * Gets all the dependencies of the given roots. The inputs must be returned * in a stable order. In other words, if A comes before B, and A does not * transitively depend on B, then A must also come before B in the returned * list. */ public List<INPUT> getSortedDependenciesOf(List<INPUT> roots) { return getDependenciesOf(roots, true); } /** * Gets all the dependencies of the given roots. The inputs must be returned * in a stable order. In other words, if A comes before B, and A does not * transitively depend on B, then A must also come before B in the returned * list. * * @param sorted If true, get them in topologically sorted order. If false, * get them in the original order they were passed to the compiler. */ public List<INPUT> getDependenciesOf(List<INPUT> roots, boolean sorted) { Preconditions.checkArgument(inputs.containsAll(roots)); Set<INPUT> included = Sets.newHashSet(); Deque<INPUT> worklist = new ArrayDeque<INPUT>(roots); while (!worklist.isEmpty()) { INPUT current = worklist.pop(); if (included.add(current)) { for (String req : current.getRequires()) { INPUT dep = provideMap.get(req); if (dep != null) { worklist.add(dep); } } } } ImmutableList.Builder<INPUT> builder = ImmutableList.builder(); for (INPUT current : (sorted ? sortedList : inputs)) { if (included.contains(current)) { builder.add(current); } } return builder.build(); } public List<INPUT> getInputsWithoutProvides() { return Collections.<INPUT>unmodifiableList(noProvides); } private static <T> List<T> topologicalStableSort( List<T> items, Multimap<T, T> deps) { if (items.size() == 0) { // Priority queue blows up if we give it a size of 0. Since we need // to special case this either way, just bail out. return Lists.newArrayList(); } final Map<T, Integer> originalIndex = Maps.newHashMap(); for (int i = 0; i < items.size(); i++) { originalIndex.put(items.get(i), i); } PriorityQueue<T> inDegreeZero = new PriorityQueue<T>(items.size(), new Comparator<T>() { @Override public int compare(T a, T b) { return originalIndex.get(a).intValue() - originalIndex.get(b).intValue(); } }); List<T> result = Lists.newArrayList(); Multiset<T> inDegree = HashMultiset.create(); Multimap<T, T> reverseDeps = ArrayListMultimap.create(); Multimaps.invertFrom(deps, reverseDeps); // First, add all the inputs with in-degree 0. for (T item : items) { Collection<T> itemDeps = deps.get(item); inDegree.add(item, itemDeps.size()); if (itemDeps.isEmpty()) { inDegreeZero.add(

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> referenceMap) { // TODO(bashir) In hot-swap version this means that for global scope we // only go through all global variables accessed in the modified file not // all global variables. This should be fixed. // Check all vars after finishing a scope for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); checkVar(t, v, referenceMap.getReferences(v).references); } } /** * If the variable is declared more than once in a basic block, generate a * warning. Also check if a variable is used in a given scope before it is * declared, which suggest a likely error. Relies on the fact that * references is in parse-tree order. */ private void checkVar(NodeTraversal t, Var v, List<Reference> references) { blocksWithDeclarations.clear(); boolean isDeclaredInScope = false; boolean isUnhoistedNamedFunction = false; Reference hoistedFn = null; // Look for hoisted functions. for (Reference reference : references) { if (reference.isHoistedFunction()) { blocksWithDeclarations.add(reference.getBasicBlock()); isDeclaredInScope = true; hoistedFn = reference; break; } else if (NodeUtil.isFunctionDeclaration( reference.getNode().getParent())) { isUnhoistedNamedFunction = true; } } for (Reference reference : references) { if (reference == hoistedFn) { continue; } BasicBlock basicBlock = reference.getBasicBlock(); boolean isDeclaration = reference.isDeclaration(); boolean allowDupe = SyntacticScopeCreator.hasDuplicateDeclarationSuppression( reference.getNode(), v); if (isDeclaration && !allowDupe) { // Look through all the declarations we've found so far, and // check if any of them are before this block. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (declaredBlock.provablyExecutesBefore(basicBlock)) { // TODO(johnlenz): Fix AST generating clients that so they would // have property StaticSourceFile attached at each node. Or // better yet, make sure the generated code never violates // the requirement to pass aggressive var check! String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), checkLevel, REDECLARED_VARIABLE, v.name)); break; } } } if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) { // Only allow an unhoisted named function to be used within the // block it is declared. for (BasicBlock declaredBlock : blocksWithDeclarations) { if (!declaredBlock.provablyExecutesBefore(basicBlock)) { String filename = NodeUtil.getSourceName(reference.getNode()); compiler.report( JSError.make(filename, reference.getNode(), AMBIGUOUS_FUNCTION_DECL, v.name)); break; } } } if (!isDeclaration && !isDeclaredInScope) { // Don't check the order of refer in externs files. if (!reference.getNode().isFromExterns()) { // Special case to deal with var goog = goog || {} Node grandparent = reference.getGrandparent(); if (grandparent.isName() && grandparent.getString() == v

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> JS code * <li>checks for undefined variables * <li>performs optimizations such as constant folding and constants inlining * <li>renames variables (to short names) * <li>outputs compact JavaScript code * </ul> * * External variables are declared in 'externs' files. For instance, the file * may include definitions for global javascript/browser objects such as * window, document. * */ public class Compiler extends AbstractCompiler { static final String SINGLETON_MODULE_NAME = "[singleton]"; static final DiagnosticType MODULE_DEPENDENCY_ERROR = DiagnosticType.error("JSC_MODULE_DEPENDENCY_ERROR", "Bad dependency: {0} -> {1}. " + "Modules must be listed in dependency order."); static final DiagnosticType MISSING_ENTRY_ERROR = DiagnosticType.error( "JSC_MISSING_ENTRY_ERROR", "required entry point \"{0}\" never provided"); private static final String CONFIG_RESOURCE = "com.google.javascript.jscomp.parsing.ParserConfig"; CompilerOptions options = null; private PassConfig passes = null; // The externs inputs private List<CompilerInput> externs; // The JS source modules private List<JSModule> modules; // The graph of the JS source modules. Must be null if there are less than // 2 modules, because we use this as a signal for which passes to run. private JSModuleGraph moduleGraph; // The JS source inputs private List<CompilerInput> inputs; // error manager to which error management is delegated private ErrorManager errorManager; // Warnings guard for filtering warnings. private WarningsGuard warningsGuard; // Compile-time injected libraries. The node points to the last node of // the library, so code can be inserted after. private final Map<String, Node> injectedLibraries = Maps.newLinkedHashMap(); // Parse tree root nodes Node externsRoot; Node jsRoot; Node externAndJsRoot; private Map<InputId, CompilerInput> inputsById; /** The source code map */ private SourceMap sourceMap; /** The externs created from the exports. */ private String externExports = null; /** * Ids for function inlining so that each declared name remains * unique. */ private int uniqueNameId = 0; /** * Whether to assume there are references to the RegExp Global object * properties. */ private boolean hasRegExpGlobalReferences = true; /** The function information map */ private FunctionInformationMap functionInformationMap; /** Debugging information */ private final StringBuilder debugLog = new StringBuilder(); /** Detects Google-specific coding conventions. */ CodingConvention defaultCodingConvention = new ClosureCodingConvention(); private JSTypeRegistry typeRegistry; private Config parserConfig = null; private ReverseAbstractInterpreter abstractInterpreter; private TypeValidator typeValidator; public PerformanceTracker tracker; // The oldErrorReporter exists so we can get errors from the JSTypeRegistry. private final com.google.javascript.rhino.ErrorReporter oldErrorReporter = RhinoErrorReporter.forOldRhino(this); // This error reporter gets the messages from the current Rhino parser. private final ErrorReporter defaultErrorReporter = RhinoErrorReporter.forNewRhino(this); /** Error strings used for reporting JSErrors */ public static final DiagnosticType OPTIMIZE_LOOP_ERROR = DiagnosticType.error( "JSC_

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>(printer); } } // DiagnosticGroups override the plain checkTypes option. if (options.enables(DiagnosticGroups.CHECK_TYPES)) { options.checkTypes = true; } else if (options.disables(DiagnosticGroups.CHECK_TYPES)) { options.checkTypes = false; } else if (!options.checkTypes) { // If DiagnosticGroups did not override the plain checkTypes // option, and checkTypes is enabled, then turn off the // parser type warnings. options.setWarningLevel( DiagnosticGroup.forType( RhinoErrorReporter.TYPE_PARSE_ERROR), CheckLevel.OFF); } if (options.checkGlobalThisLevel.isOn() && !options.disables(DiagnosticGroups.GLOBAL_THIS)) { options.setWarningLevel( DiagnosticGroups.GLOBAL_THIS, options.checkGlobalThisLevel); } if (options.getLanguageIn() == LanguageMode.ECMASCRIPT5_STRICT) { options.setWarningLevel( DiagnosticGroups.ES5_STRICT, CheckLevel.ERROR); } // Initialize the warnings guard. List<WarningsGuard> guards = Lists.newArrayList(); guards.add( new SuppressDocWarningsGuard( getDiagnosticGroups().getRegisteredGroups())); guards.add(options.getWarningsGuard()); ComposeWarningsGuard composedGuards = new ComposeWarningsGuard(guards); // All passes must run the variable check. This synthesizes // variables later so that the compiler doesn't crash. It also // checks the externs file for validity. If you don't want to warn // about missing variable declarations, we shut that specific // error off. if (!options.checkSymbols && !composedGuards.enables(DiagnosticGroups.CHECK_VARIABLES)) { composedGuards.addGuard(new DiagnosticGroupWarningsGuard( DiagnosticGroups.CHECK_VARIABLES, CheckLevel.OFF)); } this.warningsGuard = composedGuards; } /** * Initializes the instance state needed for a compile job. * @deprecated Convert your arrays to lists and use the list-based API. */ @Deprecated public void init(JSSourceFile[] externs, JSSourceFile[] inputs, CompilerOptions options) { init(Lists.<JSSourceFile>newArrayList(externs), Lists.<JSSourceFile>newArrayList(inputs), options); } /** * Initializes the instance state needed for a compile job. */ public <T1 extends SourceFile, T2 extends SourceFile> void init( List<T1> externs, List<T2> inputs, CompilerOptions options) { JSModule module = new JSModule(SINGLETON_MODULE_NAME); for (SourceFile input : inputs) { module.add(input); } initModules(externs, Lists.newArrayList(module), options); } /** * Initializes the instance state needed for a compile job if the sources * are in modules. * @deprecated Convert your arrays to lists and use the list-based API. */ @Deprecated public void init(JSSourceFile[] externs, JSModule[] modules, CompilerOptions options) { initModules(Lists.<SourceFile>newArrayList(externs), Lists.<JSModule>newArrayList(modules), options); } /** * Initializes the instance state needed for a compile job if the sources * are in modules. */ public <T extends SourceFile> void

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> initModules( List<T> externs, List<JSModule> modules, CompilerOptions options) { initOptions(options); checkFirstModule(modules); fillEmptyModules(modules); this.externs = makeCompilerInput(externs, true); // Generate the module graph, and report any errors in the module // specification as errors. this.modules = modules; if (modules.size() > 1) { try { this.moduleGraph = new JSModuleGraph(modules); } catch (JSModuleGraph.ModuleDependenceException e) { // problems with the module format. Report as an error. The // message gives all details. report(JSError.make(MODULE_DEPENDENCY_ERROR, e.getModule().getName(), e.getDependentModule().getName())); return; } } else { this.moduleGraph = null; } this.inputs = getAllInputsFromModules(modules); initBasedOnOptions(); initInputsByIdMap(); } /** * Do any initialization that is dependent on the compiler options. */ private void initBasedOnOptions() { // Create the source map if necessary. if (options.sourceMapOutputPath != null) { sourceMap = options.sourceMapFormat.getInstance(); sourceMap.setPrefixMappings(options.sourceMapLocationMappings); } } private <T extends SourceFile> List<CompilerInput> makeCompilerInput( List<T> files, boolean isExtern) { List<CompilerInput> inputs = Lists.newArrayList(); for (T file : files) { inputs.add(new CompilerInput(file, isExtern)); } return inputs; } private static final DiagnosticType EMPTY_MODULE_LIST_ERROR = DiagnosticType.error("JSC_EMPTY_MODULE_LIST_ERROR", "At least one module must be provided"); private static final DiagnosticType EMPTY_ROOT_MODULE_ERROR = DiagnosticType.error("JSC_EMPTY_ROOT_MODULE_ERROR", "Root module '{0}' must contain at least one source code input"); /** * Verifies that at least one module has been provided and that the first one * has at least one source code input. */ private void checkFirstModule(List<JSModule> modules) { if (modules.isEmpty()) { report(JSError.make(EMPTY_MODULE_LIST_ERROR)); } else if (modules.get(0).getInputs().isEmpty() && modules.size() > 1) { // The root module may only be empty if there is exactly 1 module. report(JSError.make(EMPTY_ROOT_MODULE_ERROR, modules.get(0).getName())); } } /** * Empty modules get an empty "fill" file, so that we can move code into * an empty module. */ static String createFillFileName(String moduleName) { return "[" + moduleName + "]"; } /** * Fill any empty modules with a place holder file. It makes any cross module * motion easier. */ private static void fillEmptyModules(List<JSModule> modules) { for (JSModule module : modules) { if (module.getInputs().isEmpty()) { module.add(SourceFile.fromCode( createFillFileName(module.getName()), "")); } } } /** * Rebuilds the internal list of inputs by iterating over all modules. * This is necessary if inputs have been added to

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> or removed from a module * after the {@link #init(JSSourceFile[], JSModule[], CompilerOptions)} call. */ public void rebuildInputsFromModules() { inputs = getAllInputsFromModules(modules); initInputsByIdMap(); } /** * Builds a single list of all module inputs. Verifies that it contains no * duplicates. */ private static List<CompilerInput> getAllInputsFromModules( List<JSModule> modules) { List<CompilerInput> inputs = Lists.newArrayList(); Map<String, JSModule> inputMap = Maps.newHashMap(); for (JSModule module : modules) { for (CompilerInput input : module.getInputs()) { String inputName = input.getName(); // NOTE(nicksantos): If an input is in more than one module, // it will show up twice in the inputs list, and then we // will get an error down the line. inputs.add(input); inputMap.put(inputName, module); } } return inputs; } static final DiagnosticType DUPLICATE_INPUT = DiagnosticType.error("JSC_DUPLICATE_INPUT", "Duplicate input: {0}"); static final DiagnosticType DUPLICATE_EXTERN_INPUT = DiagnosticType.error("JSC_DUPLICATE_EXTERN_INPUT", "Duplicate extern input: {0}"); /** * Creates a map to make looking up an input by name fast. Also checks for * duplicate inputs. */ void initInputsByIdMap() { inputsById = new HashMap<InputId, CompilerInput>(); for (CompilerInput input : externs) { InputId id = input.getInputId(); CompilerInput previous = putCompilerInput(id, input); if (previous != null) { report(JSError.make(DUPLICATE_EXTERN_INPUT, input.getName())); } } for (CompilerInput input : inputs) { InputId id = input.getInputId(); CompilerInput previous = putCompilerInput(id, input); if (previous != null) { report(JSError.make(DUPLICATE_INPUT, input.getName())); } } } public Result compile( SourceFile extern, SourceFile input, CompilerOptions options) { return compile(Lists.newArrayList(extern), Lists.newArrayList(input), options); } /** * @deprecated Convert your arrays to lists and use the list-based API. */ @Deprecated public Result compile( SourceFile extern, JSSourceFile[] input, CompilerOptions options) { return compile(Lists.newArrayList(extern), Lists.newArrayList(input), options); } /** * @deprecated Convert your arrays to lists and use the list-based * compileModules method. */ @Deprecated public Result compile( JSSourceFile extern, JSModule[] modules, CompilerOptions options) { return compileModules( Lists.newArrayList(extern), Lists.newArrayList(modules), options); } /** * Compiles a list of inputs. * @deprecated Convert your arrays to lists and use the list-based compile * method. */ @Deprecated public Result compile(JSSourceFile[] externs, JSSourceFile[] inputs, CompilerOptions options) { return compile(Lists.<SourceFile>newArrayList(externs), Lists.<SourceFile>newArrayList(inputs), options); } /** * Compiles a list of inputs. */ public <T1 extends SourceFile, T

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>2 extends SourceFile> Result compile( List<T1> externs, List<T2> inputs, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { init(externs, inputs, options); if (hasErrors()) { return getResult(); } return compile(); } finally { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } } /** * Compiles a list of modules. * @deprecated Convert your arrays to lists and use the list-based * compileModules method. */ @Deprecated public Result compile(JSSourceFile[] externs, JSModule[] modules, CompilerOptions options) { return compileModules(Lists.<SourceFile>newArrayList(externs), Lists.<JSModule>newArrayList(modules), options); } /** * Compiles a list of modules. */ public <T extends SourceFile> Result compileModules(List<T> externs, List<JSModule> modules, CompilerOptions options) { // The compile method should only be called once. Preconditions.checkState(jsRoot == null); try { initModules(externs, modules, options); if (hasErrors()) { return getResult(); } return compile(); } finally { Tracer t = newTracer("generateReport"); errorManager.generateReport(); stopTracer(t, "generateReport"); } } private Result compile() { return runInCompilerThread(new Callable<Result>() { @Override public Result call() throws Exception { compileInternal(); return getResult(); } }); } /** * Disable threads. This is for clients that run on AppEngine and * don't have threads. */ public void disableThreads() { useThreads = false; } @SuppressWarnings("unchecked") <T> T runInCompilerThread(final Callable<T> callable) { final boolean dumpTraceReport = options != null && options.tracer.isOn(); T result = null; final Throwable[] exception = new Throwable[1]; Callable<T> bootCompilerThread = new Callable<T>() { @Override public T call() { try { compilerThread = Thread.currentThread(); if (dumpTraceReport) { Tracer.initCurrentThreadTrace(); } return callable.call(); } catch (Throwable e) { exception[0] = e; } finally { compilerThread = null; if (dumpTraceReport) { Tracer.logAndClearCurrentThreadTrace(); } } return null; } }; Preconditions.checkState( compilerThread == null || compilerThread == Thread.currentThread(), "Please do not share the Compiler across threads"); // If the compiler thread is available, use it. if (useThreads && compilerThread == null) { try { result = compilerExecutor.submit(bootCompilerThread).get(); } catch (InterruptedException e) { throw Throwables.propagate(e); } catch (ExecutionException e) { throw Throwables.propagate(e); } } else { try { result = callable.call(); } catch (Exception e) { exception[0] = e; } } // Pass on any exception caught by the runnable object. if (exception[0] != null) { throw new RuntimeException

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> continue; } if (options.transformAMDToCJSModules) { new TransformAMDToCJSModule(this).process(null, root); } if (options.processCommonJSModules) { ProcessCommonJSModules cjs = new ProcessCommonJSModules(this, options.commonJSModulePathPrefix); cjs.process(null, root); JSModule m = cjs.getModule(); if (m != null) { modulesByName.put(m.getName(), m); modulesByInput.put(input, m); } } } if (options.processCommonJSModules) { List<JSModule> modules = Lists.newArrayList(modulesByName.values()); if (!modules.isEmpty()) { this.modules = modules; this.moduleGraph = new JSModuleGraph(this.modules); } for (JSModule module : modules) { for (CompilerInput input : module.getInputs()) { for (String require : input.getRequires()) { JSModule dependency = modulesByName.get(require); if (dependency == null) { report(JSError.make(MISSING_ENTRY_ERROR, require)); } else { module.addDependency(dependency); } } } } try { modules = Lists.newArrayList(); for (CompilerInput input : this.moduleGraph.manageDependencies( options.dependencyOptions, inputs)) { modules.add(modulesByInput.get(input)); } this.modules = modules; this.moduleGraph = new JSModuleGraph(modules); } catch (Exception e) { Throwables.propagate(e); } } } public Node parse(SourceFile file) { initCompilerOptionsIfTesting(); addToDebugLog("Parsing: " + file.getName()); return new JsAst(file).getAstRoot(this); } private int syntheticCodeId = 0; @Override Node parseSyntheticCode(String js) { CompilerInput input = new CompilerInput( SourceFile.fromCode(" [synthetic:" + (++syntheticCodeId) + "] ", js)); putCompilerInput(input.getInputId(), input); return input.getAstRoot(this); } /** * Allow subclasses to override the default CompileOptions object. */ protected CompilerOptions newCompilerOptions() { return new CompilerOptions(); } void initCompilerOptionsIfTesting() { if (options == null) { // initialization for tests that don't initialize the compiler // by the normal mechanisms. initOptions(newCompilerOptions()); } } @Override Node parseSyntheticCode(String fileName, String js) { initCompilerOptionsIfTesting(); return parse(SourceFile.fromCode(fileName, js)); } @Override Node parseTestCode(String js) { initCompilerOptionsIfTesting(); CompilerInput input = new CompilerInput( SourceFile.fromCode("[testcode]", js)); if (inputsById == null) { inputsById = Maps.newHashMap(); } putCompilerInput(input.getInputId(), input); return input.getAstRoot(this); } @Override ErrorReporter getDefaultErrorReporter() { return defaultErrorReporter; } //------------------------------------------------------------------------ // Convert back to source code //------------------------------------------------------------------------ /** * Converts the main parse tree back to JS code. */ public String toSource() { return runInCompilerThread(new Callable<String>() { @Override public

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> String call() throws Exception { Tracer tracer = newTracer("toSource"); try { CodeBuilder cb = new CodeBuilder(); if (jsRoot != null) { int i = 0; for (Node scriptNode = jsRoot.getFirstChild(); scriptNode != null; scriptNode = scriptNode.getNext()) { toSource(cb, i++, scriptNode); } } return cb.toString(); } finally { stopTracer(tracer, "toSource"); } } }); } /** * Converts the parse tree for each input back to JS code. */ public String[] toSourceArray() { return runInCompilerThread(new Callable<String[]>() { @Override public String[] call() throws Exception { Tracer tracer = newTracer("toSourceArray"); try { int numInputs = inputs.size(); String[] sources = new String[numInputs]; CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); } return sources; } finally { stopTracer(tracer, "toSourceArray"); } } }); } /** * Converts the parse tree for a module back to JS code. */ public String toSource(final JSModule module) { return runInCompilerThread(new Callable<String>() { @Override public String call() throws Exception { List<CompilerInput> inputs = module.getInputs(); int numInputs = inputs.size(); if (numInputs == 0) { return ""; } CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); if (scriptNode == null) { throw new IllegalArgumentException( "Bad module: " + module.getName()); } toSource(cb, i, scriptNode); } return cb.toString(); } }); } /** * Converts the parse tree for each input in a module back to JS code. */ public String[] toSourceArray(final JSModule module) { return runInCompilerThread(new Callable<String[]>() { @Override public String[] call() throws Exception { List<CompilerInput> inputs = module.getInputs(); int numInputs = inputs.size(); if (numInputs == 0) { return new String[0]; } String[] sources = new String[numInputs]; CodeBuilder cb = new CodeBuilder(); for (int i = 0; i < numInputs; i++) { Node scriptNode = inputs.get(i).getAstRoot(Compiler.this); if (scriptNode == null) { throw new IllegalArgumentException( "Bad module input: " + inputs.get(i).getName()); } cb.reset(); toSource(cb, i, scriptNode); sources[i] = cb.toString(); } return sources; } }); } /** * Writes out JS code from a root node. If printing input delimiters, this * method will attach a comment to the start of the text indicating which * input the output derived from. If there were any preserve annotations * within the root

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> AST * specifically for that target. */ public void processDefines() { (new DefaultPassConfig(options)).processDefines.create(this) .process(externsRoot, jsRoot); } boolean isInliningForbidden() { return options.propertyRenaming == PropertyRenamingPolicy.HEURISTIC || options.propertyRenaming == PropertyRenamingPolicy.AGGRESSIVE_HEURISTIC; } /** Control Flow Analysis. */ ControlFlowGraph<Node> computeCFG() { logger.fine("Computing Control Flow Graph"); Tracer tracer = newTracer("computeCFG"); ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false); process(cfa); stopTracer(tracer, "computeCFG"); return cfa.getCfg(); } public void normalize() { logger.fine("Normalizing"); startPass("normalize"); process(new Normalize(this, false)); endPass(); } @Override void prepareAst(Node root) { CompilerPass pass = new PrepareAst(this); pass.process(null, root); } void recordFunctionInformation() { logger.fine("Recording function information"); startPass("recordFunctionInformation"); RecordFunctionInformation recordFunctionInfoPass = new RecordFunctionInformation( this, getPassConfig().getIntermediateState().functionNames); process(recordFunctionInfoPass); functionInformationMap = recordFunctionInfoPass.getMap(); endPass(); } protected final CodeChangeHandler.RecentChange recentChange = new CodeChangeHandler.RecentChange(); private final List<CodeChangeHandler> codeChangeHandlers = Lists.<CodeChangeHandler>newArrayList(); /** Name of the synthetic input that holds synthesized externs. */ static final String SYNTHETIC_EXTERNS = "{SyntheticVarsDeclar}"; private CompilerInput synthesizedExternsInput = null; @Override void addChangeHandler(CodeChangeHandler handler) { codeChangeHandlers.add(handler); } @Override void removeChangeHandler(CodeChangeHandler handler) { codeChangeHandlers.remove(handler); } /** * All passes should call reportCodeChange() when they alter * the JS tree structure. This is verified by CompilerTestCase. * This allows us to optimize to a fixed point. */ @Override public void reportCodeChange() { for (CodeChangeHandler handler : codeChangeHandlers) { handler.reportChange(); } } @Override public CodingConvention getCodingConvention() { CodingConvention convention = options.getCodingConvention(); convention = convention != null ? convention : defaultCodingConvention; return convention; } @Override public boolean isIdeMode() { return options.ideMode; } @Override public boolean acceptEcmaScript5() { switch (options.getLanguageIn()) { case ECMASCRIPT5: case ECMASCRIPT5_STRICT: return true; } return false; } public LanguageMode languageMode() { return options.getLanguageIn(); } @Override public boolean acceptConstKeyword() { return options.acceptConstKeyword; } @Override Config getParserConfig() { if (parserConfig == null) { Config.LanguageMode mode; switch (options.getLanguageIn()) { case ECMASCRIPT3: mode = Config.LanguageMode.ECMASCRIPT3; break; case ECMASCRIPT5: mode = Config.Language

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>) { if (lineNumber < 1) { return null; } SourceFile input = getSourceFileByName(sourceName); if (input != null) { return input.getLine(lineNumber); } return null; } @Override public Region getSourceRegion(String sourceName, int lineNumber) { if (lineNumber < 1) { return null; } SourceFile input = getSourceFileByName(sourceName); if (input != null) { return input.getRegion(lineNumber); } return null; } //------------------------------------------------------------------------ // Package-private helpers //------------------------------------------------------------------------ @Override Node getNodeForCodeInsertion(JSModule module) { if (module == null) { if (inputs.isEmpty()) { throw new IllegalStateException("No inputs"); } return inputs.get(0).getAstRoot(this); } List<CompilerInput> moduleInputs = module.getInputs(); if (moduleInputs.size() > 0) { return moduleInputs.get(0).getAstRoot(this); } throw new IllegalStateException("Root module has no inputs"); } public SourceMap getSourceMap() { return sourceMap; } VariableMap getVariableMap() { return getPassConfig().getIntermediateState().variableMap; } VariableMap getPropertyMap() { return getPassConfig().getIntermediateState().propertyMap; } CompilerOptions getOptions() { return options; } FunctionInformationMap getFunctionalInformationMap() { return functionInformationMap; } /** * Sets the logging level for the com.google.javascript.jscomp package. */ public static void setLoggingLevel(Level level) { logger.setLevel(level); } /** Gets the DOT graph of the AST generated at the end of compilation. */ public String getAstDotGraph() throws IOException { if (jsRoot != null) { ControlFlowAnalysis cfa = new ControlFlowAnalysis(this, true, false); cfa.process(null, jsRoot); return DotFormatter.toDot(jsRoot, cfa.getCfg()); } else { return ""; } } @Override public ErrorManager getErrorManager() { if (options == null) { initOptions(newCompilerOptions()); } return errorManager; } @Override List<CompilerInput> getInputsInOrder() { return Collections.<CompilerInput>unmodifiableList(inputs); } /** * Returns an unmodifiable view of the compiler inputs indexed by id. */ public Map<InputId, CompilerInput> getInputsById() { return Collections.unmodifiableMap(inputsById); } /** * Gets the externs in the order in which they are being processed. */ List<CompilerInput> getExternsInOrder() { return Collections.<CompilerInput>unmodifiableList(externs); } /** * Stores the internal compiler state just before optimization is performed. * This can be saved and restored in order to efficiently optimize multiple * different output targets without having to perform checking multiple times. * * NOTE: This does not include all parts of the compiler's internal state. In * particular, SourceFiles and CompilerOptions are not recorded. In * order to recreate a Compiler instance from scratch, you would need to * call {@code init} with the same arguments as in the initial creation before * restoring intermediate state. */ public static class IntermediateState implements Serializable { private static

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> final long serialVersionUID = 1L; Node externsRoot; private Node jsRoot; private List<CompilerInput> externs; private List<CompilerInput> inputs; private List<JSModule> modules; private PassConfig.State passConfigState; private JSTypeRegistry typeRegistry; private AbstractCompiler.LifeCycleStage lifeCycleStage; private Map<String, Node> injectedLibraries; private IntermediateState() {} } /** * Returns the current internal state, excluding the input files and modules. */ public IntermediateState getState() { IntermediateState state = new IntermediateState(); state.externsRoot = externsRoot; state.jsRoot = jsRoot; state.externs = externs; state.inputs = inputs; state.modules = modules; state.passConfigState = getPassConfig().getIntermediateState(); state.typeRegistry = typeRegistry; state.lifeCycleStage = getLifeCycleStage(); state.injectedLibraries = Maps.newLinkedHashMap(injectedLibraries); return state; } /** * Sets the internal state to the capture given. Note that this assumes that * the input files are already set up. */ public void setState(IntermediateState state) { externsRoot = state.externsRoot; jsRoot = state.jsRoot; externs = state.externs; inputs = state.inputs; modules = state.modules; passes = createPassConfigInternal(); getPassConfig().setIntermediateState(state.passConfigState); typeRegistry = state.typeRegistry; setLifeCycleStage(state.lifeCycleStage); injectedLibraries.clear(); injectedLibraries.putAll(state.injectedLibraries); } @VisibleForTesting List<CompilerInput> getInputsForTesting() { return inputs; } @VisibleForTesting List<CompilerInput> getExternsForTesting() { return externs; } @Override boolean hasRegExpGlobalReferences() { return hasRegExpGlobalReferences; } @Override void setHasRegExpGlobalReferences(boolean references) { hasRegExpGlobalReferences = references; } @Override void updateGlobalVarReferences(Map<Var, ReferenceCollection> refMapPatch, Node collectionRoot) { Preconditions.checkState(collectionRoot.isScript() || collectionRoot.isBlock()); if (globalRefMap == null) { globalRefMap = new GlobalVarReferenceMap(getInputsInOrder(), getExternsInOrder()); } globalRefMap.updateGlobalVarReferences(refMapPatch, collectionRoot); } @Override GlobalVarReferenceMap getGlobalVarReferences() { return globalRefMap; } @Override CompilerInput getSynthesizedExternsInput() { if (synthesizedExternsInput == null) { synthesizedExternsInput = newExternInput(SYNTHETIC_EXTERNS); } return synthesizedExternsInput; } @Override public double getProgress() { return progress; } @Override void setProgress(double newProgress) { if (newProgress > 1.0) { progress = 1.0; } else if (newProgress < 0.0) { progress = 0.0; } else { progress = newProgress; } } /** * Replaces one file in a hot-swap mode. The given JsAst should be made * from a

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> works correctly through * typedefs. Alternatively, we would need to walk through the parse tree and * unroll each reference to a {@code NamedType} to its resolved type before * applying the rest of the analysis.<p> * * TODO(user): Revisit all of this logic.<p> * * The existing typing logic is hacky. Unresolved types should get processed * in a more consistent way, but with the Rhino merge coming, there will be * much that has to be changed.<p> * */ class NamedType extends ProxyObjectType { private static final long serialVersionUID = 1L; private final String reference; private final String sourceName; private final int lineno; private final int charno; /** * Validates the type resolution. */ private Predicate<JSType> validator; /** * Property-defining continuations. */ private List<PropertyContinuation> propertyContinuations = null; /** * Create a named type based on the reference. */ NamedType(JSTypeRegistry registry, String reference, String sourceName, int lineno, int charno) { super(registry, registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE)); Preconditions.checkNotNull(reference); this.reference = reference; this.sourceName = sourceName; this.lineno = lineno; this.charno = charno; } @Override boolean defineProperty(String propertyName, JSType type, boolean inferred, Node propertyNode) { if (!isResolved()) { // If this is an unresolved object type, we need to save all its // properties and define them when it is resolved. if (propertyContinuations == null) { propertyContinuations = Lists.newArrayList(); } propertyContinuations.add( new PropertyContinuation( propertyName, type, inferred, propertyNode)); return true; } else { return super.defineProperty( propertyName, type, inferred, propertyNode); } } private void finishPropertyContinuations() { ObjectType referencedObjType = getReferencedObjTypeInternal(); if (referencedObjType != null && !referencedObjType.isUnknownType()) { if (propertyContinuations != null) { for (PropertyContinuation c : propertyContinuations) { c.commit(this); } } } propertyContinuations = null; } /** Returns the type to which this refers (which is unknown if unresolved). */ public JSType getReferencedType() { return getReferencedTypeInternal(); } @Override public String getReferenceName() { return reference; } @Override String toStringHelper(boolean forAnnotations) { return reference; } @Override public boolean hasReferenceName() { return true; } @Override boolean isNamedType() { return true; } @Override public boolean isNominalType() { return true; } /** * Two named types are equivalent if they are the same {@code * ObjectType} object. This is complicated by the fact that isEquivalent * is sometimes called before we have a chance to resolve the type * names. * * @return {@code true} iff {@code that} == {@code this} or {@code that} * is a {@link NamedType} whose reference is the same as ours, * or {@code that} is the type we reference. */ @Override public boolean isEquivalentTo(JSType that) { if (this ==

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>>Flow Equations: Implement * {@link #flowThrough(Object, LatticeElement)}. * <li>Initial Entry Value: Implement {@link #createEntryLattice()}. * <li>Initial Estimate: Implement {@link #createInitialEstimateLattice()}. * </ol> * * <p>Upon execution of the {@link #analyze()} method, nodes of the input * control flow graph will be annotated with a {@link FlowState} object that * represents maximum fixed point solution. Any previous annotations at the * nodes of the control flow graph will be lost. * * * @param <N> The control flow graph's node value type. * @param <L> Lattice element type. */ abstract class DataFlowAnalysis<N, L extends LatticeElement> { private final ControlFlowGraph<N> cfg; final JoinOp<L> joinOp; protected final Set<DiGraphNode<N, Branch>> orderedWorkSet; /* * Feel free to increase this to a reasonable number if you are finding that * more and more passes need more than 200000 steps before finding a * fixed-point. If you just have a special case, consider calling * {@link #analyse(int)} instead. */ public static final int MAX_STEPS = 200000; /** * Constructs a data flow analysis. * * <p>Typical usage * <pre> * DataFlowAnalysis dfa = ... * dfa.analyze(); * </pre> * * {@link #analyze()} annotates the result to the control flow graph by * means of {@link DiGraphNode#setAnnotation} without any * modification of the graph itself. Additional calls to {@link #analyze()} * recomputes the analysis which can be useful if the control flow graph * has been modified. * * @param targetCfg The control flow graph object that this object performs * on. Modification of the graph requires a separate call to * {@link #analyze()}. * * @see #analyze() */ DataFlowAnalysis(ControlFlowGraph<N> targetCfg, JoinOp<L> joinOp) { this.cfg = targetCfg; this.joinOp = joinOp; Comparator<DiGraphNode<N, Branch>> nodeComparator = cfg.getOptionalNodeComparator(isForward()); if (nodeComparator != null) { this.orderedWorkSet = Sets.newTreeSet(nodeComparator); } else { this.orderedWorkSet = Sets.newLinkedHashSet(); } } /** * Returns the control flow graph that this analysis was performed on. * Modifications can be done on this graph, however, the only time that the * annotations are correct is after {@link #analyze()} is called and before * the graph has been modified. */ final ControlFlowGraph<N> getCfg() { return cfg; } /** * Returns the lattice element at the exit point. */ L getExitLatticeElement() { DiGraphNode<N, Branch> node = getCfg().getImplicitReturn(); FlowState<L> state = node.getAnnotation(); return state.getIn(); } @SuppressWarnings("unchecked") protected L join(L latticeA, L latticeB) { return joinOp.apply(Lists.<L>newArrayList(latticeA, latticeB)); } /** * Checks whether the analysis is a forward flow analysis

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> or backward flow * analysis. * * @return {@code true} if it is a forward analysis. */ abstract boolean isForward(); /** * Computes the output state for a given node and input state. * * @param node The node. * @param input Input lattice that should be read-only. * @return Output lattice. */ abstract L flowThrough(N node, L input); /** * Finds a fixed-point solution using at most {@link #MAX_STEPS} * iterations. * * @see #analyze(int) */ final void analyze() { analyze(MAX_STEPS); } /** * Finds a fixed-point solution. The function has the side effect of replacing * the existing node annotations with the computed solutions using {@link * com.google.javascript.jscomp.graph.GraphNode#setAnnotation(Annotation)}. * * <p>Initially, each node's input and output flow state contains the value * given by {@link #createInitialEstimateLattice()} (with the exception of the * entry node of the graph which takes on the {@link #createEntryLattice()} * value. Each node will use the output state of its predecessor and compute a * output state according to the instruction. At that time, any nodes that * depends on the node's newly modified output value will need to recompute * their output state again. Each step will perform a computation at one node * until no extra computation will modify any existing output state anymore. * * @param maxSteps Max number of iterations before the method stops and throw * a {@link MaxIterationsExceededException}. This will prevent the * analysis from going into a infinite loop. */ final void analyze(int maxSteps) { initialize(); int step = 0; while (!orderedWorkSet.isEmpty()) { if (step > maxSteps) { throw new MaxIterationsExceededException( "Analysis did not terminate after " + maxSteps + " iterations"); } DiGraphNode<N, Branch> curNode = orderedWorkSet.iterator().next(); orderedWorkSet.remove(curNode); joinInputs(curNode); if (flow(curNode)) { // If there is a change in the current node, we want to grab the list // of nodes that this node affects. List<DiGraphNode<N, Branch>> nextNodes = isForward() ? cfg.getDirectedSuccNodes(curNode) : cfg.getDirectedPredNodes(curNode); for (DiGraphNode<N, Branch> nextNode : nextNodes) { if (nextNode != cfg.getImplicitReturn()) { orderedWorkSet.add(nextNode); } } } step++; } if (isForward()) { joinInputs(getCfg().getImplicitReturn()); } } /** * Gets the state of the initial estimation at each node. * * @return Initial state. */ abstract L createInitialEstimateLattice(); /** * Gets the incoming state of the entry node. * * @return Entry state. */ abstract L createEntryLattice(); /** * Initializes the work list and the control flow graph. */ protected void initialize() { // TODO(user): Calling clear doesn't deallocate the memory in a // LinkedHashSet. Consider creating a new work set if we plan to repeatedly // call analyze. orderedWorkSet.clear(); for (Di

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>GraphNode<N, Branch> node : cfg.getDirectedGraphNodes()) { node.setAnnotation(new FlowState<L>(createInitialEstimateLattice(), createInitialEstimateLattice())); if (node != cfg.getImplicitReturn()) { orderedWorkSet.add(node); } } } /** * Performs a single flow through a node. * * @return {@code true} if the flow state differs from the previous state. */ protected boolean flow(DiGraphNode<N, Branch> node) { FlowState<L> state = node.getAnnotation(); if (isForward()) { L outBefore = state.out; state.out = flowThrough(node.getValue(), state.in); return !outBefore.equals(state.out); } else { L inBefore = state.in; state.in = flowThrough(node.getValue(), state.out); return !inBefore.equals(state.in); } } /** * Computes the new flow state at a given node's entry by merging the * output (input) lattice of the node's predecessor (successor). * * @param node Node to compute new join. */ protected void joinInputs(DiGraphNode<N, Branch> node) { FlowState<L> state = node.getAnnotation(); if (isForward()) { if (cfg.getEntry() == node) { state.setIn(createEntryLattice()); } else { List<DiGraphNode<N, Branch>> inNodes = cfg.getDirectedPredNodes(node); if (inNodes.size() == 1) { FlowState<L> inNodeState = inNodes.get(0).getAnnotation(); state.setIn(inNodeState.getOut()); } else if (inNodes.size() > 1) { List<L> values = new ArrayList<L>(inNodes.size()); for (DiGraphNode<N, Branch> currentNode : inNodes) { FlowState<L> currentNodeState = currentNode.getAnnotation(); values.add(currentNodeState.getOut()); } state.setIn(joinOp.apply(values)); } } } else { List<DiGraphNode<N, Branch>> inNodes = cfg.getDirectedSuccNodes(node); if (inNodes.size() == 1) { DiGraphNode<N, Branch> inNode = inNodes.get(0); if (inNode == cfg.getImplicitReturn()) { state.setOut(createEntryLattice()); } else { FlowState<L> inNodeState = inNode.getAnnotation(); state.setOut(inNodeState.getIn()); } } else if (inNodes.size() > 1) { List<L> values = new ArrayList<L>(inNodes.size()); for (DiGraphNode<N, Branch> currentNode : inNodes) { FlowState<L> currentNodeState = currentNode.getAnnotation(); values.add(currentNodeState.getIn()); } state.setOut(joinOp.apply(values)); } } } /** * The in and out states of a node. * * @param <L> Input and output lattice element type. */ static class FlowState<L extends LatticeElement> implements Annotation { private L in; private L out; /** * Private constructor. No other classes should create new states

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>. * * @param inState Input. * @param outState Output. */ private FlowState(L inState, L outState) { Preconditions.checkNotNull(inState); Preconditions.checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { Preconditions.checkNotNull(in); this.in = in; } L getOut() { return out; } void setOut(L out) { Preconditions.checkNotNull(out); this.out = out; } @Override public String toString() { return String.format("IN: %s OUT: %s", in, out); } @Override public int hashCode() { return Objects.hashCode(in, out); } } /** * The exception to be thrown if the analysis has been running for a long * number of iterations. Chances are the analysis is not monotonic, a * fixed-point cannot be found and it is currently stuck in an infinite loop. */ static class MaxIterationsExceededException extends RuntimeException { private static final long serialVersionUID = 1L; MaxIterationsExceededException(String msg) { super(msg); } } abstract static class BranchedForwardDataFlowAnalysis <N, L extends LatticeElement> extends DataFlowAnalysis<N, L> { @Override protected void initialize() { orderedWorkSet.clear(); for (DiGraphNode<N, Branch> node : getCfg().getDirectedGraphNodes()) { int outEdgeCount = getCfg().getOutEdges(node.getValue()).size(); List<L> outLattices = Lists.newArrayList(); for (int i = 0; i < outEdgeCount; i++) { outLattices.add(createInitialEstimateLattice()); } node.setAnnotation(new BranchedFlowState<L>( createInitialEstimateLattice(), outLattices)); if (node != getCfg().getImplicitReturn()) { orderedWorkSet.add(node); } } } BranchedForwardDataFlowAnalysis(ControlFlowGraph<N> targetCfg, JoinOp<L> joinOp) { super(targetCfg, joinOp); } /** * Returns the lattice element at the exit point. Needs to be overridden * because we use a BranchedFlowState instead of a FlowState; ugh. */ @Override L getExitLatticeElement() { DiGraphNode<N, Branch> node = getCfg().getImplicitReturn(); BranchedFlowState<L> state = node.getAnnotation(); return state.getIn(); } @Override final boolean isForward() { return true; } /** * The branched flow function maps a single lattice to a list of output * lattices. * * <p>Each outgoing edge of a node will have a corresponding output lattice * in the ordered returned by * {@link com.google.javascript.jscomp.graph.DiGraph#getOutEdges(Object)} * in the returned list. * * @return A list of output values depending on the edge's branch type. */ abstract List<L> branchedFlowThrough(N node, L input); @Override protected final boolean flow(DiGraphNode<N, Branch> node) { Branched

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>FlowState<L> state = node.getAnnotation(); List<L> outBefore = state.out; state.out = branchedFlowThrough(node.getValue(), state.in); Preconditions.checkState(outBefore.size() == state.out.size()); for (int i = 0; i < outBefore.size(); i++) { if (!outBefore.get(i).equals(state.out.get(i))) { return true; } } return false; } @Override protected void joinInputs(DiGraphNode<N, Branch> node) { BranchedFlowState<L> state = node.getAnnotation(); List<DiGraphNode<N, Branch>> predNodes = getCfg().getDirectedPredNodes(node); List<L> values = new ArrayList<L>(predNodes.size()); for (DiGraphNode<N, Branch> predNode : predNodes) { BranchedFlowState<L> predNodeState = predNode.getAnnotation(); L in = predNodeState.out.get( getCfg().getDirectedSuccNodes(predNode).indexOf(node)); values.add(in); } if (getCfg().getEntry() == node) { state.setIn(createEntryLattice()); } else if (!values.isEmpty()) { state.setIn(joinOp.apply(values)); } } } /** * The in and out states of a node. * * @param <L> Input and output lattice element type. */ static class BranchedFlowState<L extends LatticeElement> implements Annotation { private L in; private List<L> out; /** * Private constructor. No other classes should create new states. * * @param inState Input. * @param outState Output. */ private BranchedFlowState(L inState, List<L> outState) { Preconditions.checkNotNull(inState); Preconditions.checkNotNull(outState); this.in = inState; this.out = outState; } L getIn() { return in; } void setIn(L in) { Preconditions.checkNotNull(in); this.in = in; } List<L> getOut() { return out; } void setOut(List<L> out) { Preconditions.checkNotNull(out); for (L item : out) { Preconditions.checkNotNull(item); } this.out = out; } @Override public String toString() { return String.format("IN: %s OUT: %s", in, out); } @Override public int hashCode() { return Objects.hashCode(in, out); } } /** * Compute set of escaped variables. When a variable is escaped in a * dataflow analysis, it can be reference outside of the code that we are * analyzing. A variable is escaped if any of the following is true: * * <p><ol> * <li>It is defined as the exception name in CATCH clause so it became a * variable local not to our definition of scope.</li> * <li>Exported variables as they can be needed after the script terminates. * </li> * <li>Names of named functions because in JavaScript, <i>function foo(){}</i> * does not kill <i>foo</i> in the dataflow

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>_"; /** * Pattern for unnamed messages. * <p> * All JS messages in JS code should have unique name but messages in * generated code (i.e. from soy template) could have duplicated message names. * Later we replace the message names with ids constructed as a hash of the * message content. * <p> * <link href="http://code.google.com/p/closure-templates/"> * Soy</link> generates messages with names MSG_UNNAMED_<NUMBER> . This * pattern recognizes such messages. */ private static final Pattern MSG_UNNAMED_PATTERN = Pattern.compile("MSG_UNNAMED_\\d+"); private static final Pattern CAMELCASE_PATTERN = Pattern.compile("[a-z][a-zA-Z\\d]*[_\\d]*"); static final String HIDDEN_DESC_PREFIX = "@hidden"; // For old-style JS messages private static final String DESC_SUFFIX = "_HELP"; private final boolean needToCheckDuplications; private final JsMessage.Style style; private final JsMessage.IdGenerator idGenerator; final AbstractCompiler compiler; /** * The names encountered associated with their defining node and source. We * use it for tracking duplicated message ids in the source code. */ private final Map<String, MessageLocation> messageNames = Maps.newHashMap(); /** * List of found goog.getMsg call nodes. * * When we visit goog.getMsg() node we add a pair node:sourcename and later * when we visit its parent we remove this pair. All nodes that are left at * the end of traversing are orphaned nodes. It means have no corresponding * var or property node. */ private final Map<Node, String> googMsgNodes = Maps.newHashMap(); private final CheckLevel checkLevel; /** * Creates JS message visitor. * * @param compiler the compiler instance * @param needToCheckDuplications whether to check duplicated messages in * traversed * @param style style that should be used during parsing * @param idGenerator generator that used for creating unique ID for the * message */ JsMessageVisitor(AbstractCompiler compiler, boolean needToCheckDuplications, JsMessage.Style style, JsMessage.IdGenerator idGenerator) { this.compiler = compiler; this.needToCheckDuplications = needToCheckDuplications; this.style = style; this.idGenerator = idGenerator; checkLevel = (style == JsMessage.Style.CLOSURE) ? CheckLevel.ERROR : CheckLevel.WARNING; // TODO(anatol): add flag that decides whether to process UNNAMED messages. // Some projects would not want such functionality (unnamed) as they don't // use SOY templates. } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) { compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(), checkLevel, MESSAGE_NODE_IS_ORPHANED)); } } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { String messageKey; boolean isVar; Node msgNode, msgNodeParent; switch (node.getType()) { case Token.NAME:

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> suggestion = "Consider casting " + qName + " if you know it's type."; } } else { List<String> errors = Lists.newArrayList(); printErrorLocations(errors, jsType); if (!errors.isEmpty()) { suggestion = "Consider fixing errors for the following types:\n"; suggestion += Joiner.on("\n").join(errors); } } } compiler.report(JSError.make( t.getSourceName(), n, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), suggestion)); } } } /** * Processes a OBJECTLIT node. */ private void handleObjectLit(NodeTraversal t, Node n) { Node child = n.getFirstChild(); while (child != null) { // Maybe STRING, GET, SET // We should never see a mix of numbers and strings. String name = child.getString(); T type = typeSystem.getType(getScope(), n, name); Property prop = getProperty(name); if (!prop.scheduleRenaming(child, processProperty(t, prop, type, null))) { // TODO(user): It doesn't look like the user can do much in this // case right now. if (propertiesToErrorFor.containsKey(name)) { compiler.report(JSError.make( t.getSourceName(), child, propertiesToErrorFor.get(name), Warnings.INVALIDATION, name, (type == null ? "null" : type.toString()), n.toString(), "")); } } child = child.getNext(); } } private void printErrorLocations(List<String> errors, JSType t) { if (!t.isObject() || t.isAllType()) { return; } if (t.isUnionType()) { for (JSType alt : t.toMaybeUnionType().getAlternates()) { printErrorLocations(errors, alt); } return; } for (JSError error : invalidationMap.get(t)) { if (errors.size() > MAX_INVALDIATION_WARNINGS_PER_PROPERTY) { return; } errors.add( t.toString() + " at " + error.sourceName + ":" + error.lineNumber); } } /** * Processes a property, adding it to the list of properties to rename. * @return a representative type for the property reference, which will be * the highest type on the prototype chain of the provided type. In the * case of a union type, it will be the highest type on the prototype * chain of one of the members of the union. */ private T processProperty( NodeTraversal t, Property prop, T type, T relatedType) { type = typeSystem.restrictByNotNullOrUndefined(type); if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) { return null; } Iterable<T> alternatives = typeSystem.getTypeAlternatives(type); if (alternatives != null) { T firstType = relatedType; for (T subType : alternatives) { T lastType = processProperty(t, prop, subType, firstType); if (lastType != null) { firstType = firstType == null ? lastType : firstType; } } return

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> firstType; } else { T topType = typeSystem.getTypeWithProperty(prop.name, type); if (typeSystem.isInvalidatingType(topType)) { return null; } prop.addType(type, topType, relatedType); return topType; } } } /** Renames all properties with references on more than one type. */ void renameProperties() { int propsRenamed = 0, propsSkipped = 0, instancesRenamed = 0, instancesSkipped = 0, singleTypeProps = 0; for (Property prop : properties.values()) { if (prop.shouldRename()) { Map<T, String> propNames = buildPropNames(prop.getTypes(), prop.name); ++propsRenamed; prop.expandTypesToSkip(); UnionFind<T> types = prop.getTypes(); for (Node node : prop.renameNodes) { T rootType = prop.rootTypes.get(node); if (prop.shouldRename(rootType)) { String newName = propNames.get(rootType); node.setString(newName); compiler.reportCodeChange(); ++instancesRenamed; } else { ++instancesSkipped; } } } else { if (prop.skipRenaming) { ++propsSkipped; } else { ++singleTypeProps; } } } logger.fine("Renamed " + instancesRenamed + " instances of " + propsRenamed + " properties."); logger.fine("Skipped renaming " + instancesSkipped + " invalidated " + "properties, " + propsSkipped + " instances of properties " + "that were skipped for specific types and " + singleTypeProps + " properties that were referenced from only one type."); } /** * Chooses a name to use for renaming in each equivalence class and maps * each type in that class to it. */ private Map<T, String> buildPropNames(UnionFind<T> types, String name) { Map<T, String> names = Maps.newHashMap(); for (Set<T> set : types.allEquivalenceClasses()) { checkState(!set.isEmpty()); String typeName = null; for (T type : set) { if (typeName == null || type.toString().compareTo(typeName) < 0) { typeName = type.toString(); } } String newName; if ("{...}".equals(typeName)) { newName = name; } else { newName = typeName.replaceAll("[^\\w$]", "_") + "$" + name; } for (T type : set) { names.put(type, newName); } } return names; } /** Returns a map from field name to types for which it will be renamed. */ Multimap<String, Collection<T>> getRenamedTypesForTesting() { Multimap<String, Collection<T>> ret = HashMultimap.create(); for (Map.Entry<String, Property> entry: properties.entrySet()) { Property prop = entry.getValue(); if (!prop.skipRenaming) { for (Collection<T> c : prop.getTypes().allEquivalenceClasses()) { if (!c.isEmpty() && !prop.typesToSkip.contains(c.iterator().next())) { ret.put(entry.getKey(), c); } } } } return ret; }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>), registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE), registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE), registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE), registry.getNativeType(JSTypeNative.UNKNOWN_TYPE)); } @Override public void addInvalidatingType(JSType type) { checkState(!type.isUnionType()); invalidatingTypes.add(type); } @Override public StaticScope<JSType> getRootScope() { return null; } @Override public StaticScope<JSType> getFunctionScope(Node node) { return null; } @Override public JSType getType( StaticScope<JSType> scope, Node node, String prop) { if (node.getJSType() == null) { return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE); } return node.getJSType(); } @Override public boolean isInvalidatingType(JSType type) { if (type == null || invalidatingTypes.contains(type) || type.isUnknownType() /* unresolved types */) { return true; } ObjectType objType = ObjectType.cast(type); return objType != null && !objType.hasReferenceName(); } @Override public ImmutableSet<JSType> getTypesToSkipForType(JSType type) { type = type.restrictByNotNullOrUndefined(); if (type.isUnionType()) { Set<JSType> types = Sets.newHashSet(type); for (JSType alt : type.toMaybeUnionType().getAlternates()) { types.addAll(getTypesToSkipForTypeNonUnion(type)); } return ImmutableSet.copyOf(types); } else if (type.isEnumElementType()) { return getTypesToSkipForType( type.toMaybeEnumElementType().getPrimitiveType()); } return ImmutableSet.copyOf(getTypesToSkipForTypeNonUnion(type)); } private Set<JSType> getTypesToSkipForTypeNonUnion(JSType type) { Set<JSType> types = Sets.newHashSet(); JSType skipType = type; while (skipType != null) { types.add(skipType); ObjectType objSkipType = skipType.toObjectType(); if (objSkipType != null) { skipType = objSkipType.getImplicitPrototype(); } else { break; } } return types; } @Override public boolean isTypeToSkip(JSType type) { return type.isEnumType() || (type.autoboxesTo() != null); } @Override public JSType restrictByNotNullOrUndefined(JSType type) { return type.restrictByNotNullOrUndefined(); } @Override public Iterable<JSType> getTypeAlternatives(JSType type) { if (type.isUnionType()) { return type.toMaybeUnionType().getAlternates(); } else { ObjectType objType = type.toObjectType(); if (objType != null && objType.getConstructor() != null && objType.getConstructor().isInterface()) { List<JSType> list = Lists.newArrayList(); for (FunctionType impl : registry.getDirectImplementors(objType)) { list.add(impl.getInstanceType()); } return list; } else { return null; } } } @Override public ObjectType getTypeWithProperty(

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Stack) { String newName = names.getReplacementName(oldName); if (newName != null) { return newName; } } return null; } /** * Traverses the current scope and collects declared names. Does not * decent into functions or add CATCH exceptions. */ private void findDeclaredNames(Node n, Node parent, Renamer renamer) { // Do a shallow traversal, so don't traverse into function declarations, // except for the name of the function itself. if (parent == null || !parent.isFunction() || n == parent.getFirstChild()) { if (NodeUtil.isVarDeclaration(n)) { renamer.addDeclaredName(n.getString()); } else if (NodeUtil.isFunctionDeclaration(n)) { Node nameNode = n.getFirstChild(); renamer.addDeclaredName(nameNode.getString()); } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { findDeclaredNames(c, n, renamer); } } } /** * Declared names renaming policy interface. */ interface Renamer { /** * Called when a declared name is found in the local current scope. */ void addDeclaredName(String name); /** * @return A replacement name, null if oldName is unknown or should not * be replaced. */ String getReplacementName(String oldName); /** * @return Whether the constant-ness of a name should be removed. */ boolean stripConstIfReplaced(); /** * @return A Renamer for a scope within the scope of the current Renamer. */ Renamer forChildScope(); } /** * Inverts the transformation by {@link ContextualRenamer}, when possible. */ static class ContextualRenameInverter implements ScopedCallback, CompilerPass { private final AbstractCompiler compiler; // The set of names referenced in the current scope. private Set<String> referencedNames = ImmutableSet.of(); // Stack reference sets. private Deque<Set<String>> referenceStack = new ArrayDeque<Set<String>>(); // Name are globally unique initially, so we don't need a per-scope map. private Map<String, List<Node>> nameMap = Maps.newHashMap(); private ContextualRenameInverter(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node js) { NodeTraversal.traverse(compiler, js, this); } public static String getOrginalName(String name) { int index = indexOfSeparator(name); return (index == -1) ? name : name.substring(0, index); } private static int indexOfSeparator(String name) { return name.lastIndexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR); } private boolean containsSeparator(String name) { return name.indexOf(ContextualRenamer.UNIQUE_ID_SEPARATOR) != -1; } /** * Prepare a set for the new scope. */ @Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } referenceStack.push(referencedNames); referencedNames = Sets.newHashSet(); } /** * Rename vars for the current scope, and merge any referenced * names into the parent scope reference set. */ @Override public

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> void exitScope(NodeTraversal t) { if (t.inGlobalScope()) { return; } for (Iterator<Var> it = t.getScope().getVars(); it.hasNext();) { Var v = it.next(); handleScopeVar(v); } // Merge any names that were referenced but not declared in the current // scope. Set<String> current = referencedNames; referencedNames = referenceStack.pop(); // If there isn't anything left in the stack we will be going into the // global scope: don't try to build a set of referenced names for the // global scope. if (!referenceStack.isEmpty()) { referencedNames.addAll(current); } } /** * For the Var declared in the current scope determine if it is possible * to revert the name to its original form without conflicting with other * values. */ void handleScopeVar(Var v) { String name = v.getName(); if (containsSeparator(name) && !getOrginalName(name).isEmpty()) { String newName = findReplacementName(name); referencedNames.remove(name); // Adding a reference to the new name to prevent either the parent // scopes or the current scope renaming another var to this new name. referencedNames.add(newName); List<Node> references = nameMap.get(name); Preconditions.checkState(references != null); for (Node n : references) { Preconditions.checkState(n.isName()); n.setString(newName); } compiler.reportCodeChange(); nameMap.remove(name); } } /** * Find a name usable in the local scope. */ private String findReplacementName(String name) { String original = getOrginalName(name); String newName = original; int i = 0; while (!isValidName(newName)) { newName = original + ContextualRenamer.UNIQUE_ID_SEPARATOR + String.valueOf(i++); } return newName; } /** * @return Whether the name is valid to use in the local scope. */ private boolean isValidName(String name) { if (TokenStream.isJSIdentifier(name) && !referencedNames.contains(name) && !name.equals(ARGUMENTS)) { return true; } return false; } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } @Override public void visit(NodeTraversal t, Node node, Node parent) { if (t.inGlobalScope()) { return; } if (NodeUtil.isReferenceName(node)) { String name = node.getString(); // Add all referenced names to the set so it is possible to check for // conflicts. referencedNames.add(name); // Store only references to candidate names in the node map. if (containsSeparator(name)) { addCandidateNameReference(name, node); } } } private void addCandidateNameReference(String name, Node n) { List<Node> nodes = nameMap.get(name); if (null == nodes) { nodes = Lists.newLinkedList(); nameMap.put(name, nodes); } nodes.add(n); } } /** * Rename every locally name to be unique, the first encountered declaration * (specifically global names) are left in

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> PreprocessorSymbolTable getPreprocessorSymbolTable() { return preprocessorSymbolTable; } void maybeInitializePreprocessorSymbolTable(AbstractCompiler compiler) { if (options.ideMode) { Node root = compiler.getRoot(); if (preprocessorSymbolTable == null || preprocessorSymbolTable.getRootNode() != root) { preprocessorSymbolTable = new PreprocessorSymbolTable(root); } } } @Override protected List<PassFactory> getChecks() { List<PassFactory> checks = Lists.newArrayList(); checks.add(createEmptyPass("beforeStandardChecks")); if (options.closurePass) { checks.add(closureGoogScopeAliases); } if (options.nameAnonymousFunctionsOnly) { if (options.anonymousFunctionNaming == AnonymousFunctionNamingPolicy.MAPPED) { checks.add(nameMappedAnonymousFunctions); } else if (options.anonymousFunctionNaming == AnonymousFunctionNamingPolicy.UNMAPPED) { checks.add(nameUnmappedAnonymousFunctions); } return checks; } if (options.jqueryPass) { checks.add(jqueryAliases.makeOneTimePass()); } checks.add(checkSideEffects); if (options.checkSuspiciousCode || options.enables(DiagnosticGroups.GLOBAL_THIS) || options.enables(DiagnosticGroups.DEBUGGER_STATEMENT_PRESENT)) { checks.add(suspiciousCode); } if (options.checkControlStructures || options.enables(DiagnosticGroups.ES5_STRICT)) { checks.add(checkControlStructures); } if (options.checkRequires.isOn()) { checks.add(checkRequires); } if (options.checkProvides.isOn()) { checks.add(checkProvides); } // The following passes are more like "preprocessor" passes. // It's important that they run before most checking passes. // Perhaps this method should be renamed? if (options.generateExports) { checks.add(generateExports); } if (options.exportTestFunctions) { checks.add(exportTestFunctions); } if (options.closurePass) { checks.add(closurePrimitives.makeOneTimePass()); } if (options.closurePass && options.checkMissingGetCssNameLevel.isOn()) { checks.add(closureCheckGetCssName); } if (options.syntheticBlockStartMarker != null) { // This pass must run before the first fold constants pass. checks.add(createSyntheticBlocks); } checks.add(checkVars); if (options.computeFunctionSideEffects) { checks.add(checkRegExp); } if (options.aggressiveVarCheck.isOn()) { checks.add(checkVariableReferences); } // This pass should run before types are assigned. if (options.processObjectPropertyString) { checks.add(objectPropertyStringPreprocess); } if (options.checkTypes || options.inferTypes) { checks.add(resolveTypes.makeOneTimePass()); checks.add(inferTypes.makeOneTimePass()); if (options.checkTypes) { checks.add(checkTypes.makeOneTimePass()); } else { checks.add(inferJsDocInfo.makeOneTimePass()); } // We assume that only IDE-mode clients will try to query the // typed scope creator after the compile job.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> if (!options.ideMode && !options.saveDataStructures) { checks.add(clearTypedScopePass.makeOneTimePass()); } } if (options.checkUnreachableCode.isOn() || (options.checkTypes && options.checkMissingReturn.isOn())) { checks.add(checkControlFlow); } // CheckAccessControls only works if check types is on. if (options.checkTypes && (options.enables(DiagnosticGroups.ACCESS_CONTROLS) || options.enables(DiagnosticGroups.CONSTANT_PROPERTY))) { checks.add(checkAccessControls); } if (options.checkGlobalNamesLevel.isOn()) { checks.add(checkGlobalNames); } if (options.enables(DiagnosticGroups.ES5_STRICT) || options.checkCaja) { checks.add(checkStrictMode); } // Replace 'goog.getCssName' before processing defines but after the // other checks have been done. if (options.closurePass) { checks.add(closureReplaceGetCssName); } // i18n // If you want to customize the compiler to use a different i18n pass, // you can create a PassConfig that calls replacePassFactory // to replace this. checks.add(options.messageBundle != null ? replaceMessages : createEmptyPass("replaceMessages")); if (options.getTweakProcessing().isOn()) { checks.add(processTweaks); } // Defines in code always need to be processed. checks.add(processDefines); if (options.instrumentationTemplate != null || options.recordFunctionInformation) { checks.add(computeFunctionNames); } if (options.nameReferenceGraphPath != null && !options.nameReferenceGraphPath.isEmpty()) { checks.add(printNameReferenceGraph); } if (options.nameReferenceReportPath != null && !options.nameReferenceReportPath.isEmpty()) { checks.add(printNameReferenceReport); } assertAllOneTimePasses(checks); return checks; } @Override protected List<PassFactory> getOptimizations() { List<PassFactory> passes = Lists.newArrayList(); passes.add(garbageCollectChecks); // TODO(nicksantos): The order of these passes makes no sense, and needs // to be re-arranged. if (options.runtimeTypeCheck) { passes.add(runtimeTypeCheck); } passes.add(createEmptyPass("beforeStandardOptimizations")); if (options.replaceIdGenerators) { passes.add(replaceIdGenerators); } // Optimizes references to the arguments variable. if (options.optimizeArgumentsArray) { passes.add(optimizeArgumentsArray); } // Abstract method removal works best on minimally modified code, and also // only needs to run once. if (options.closurePass && (options.removeAbstractMethods || options.removeClosureAsserts)) { passes.add(closureCodeRemoval); } // Collapsing properties can undo constant inlining, so we do this before // the main optimization loop. if (options.collapseProperties) { passes.add(collapseProperties); } // ReplaceStrings runs after CollapseProperties in order to simplify // pulling in values of constants defined in enums structures. if (!options.replaceStringsFunctionDescriptions.isEmpty()) { passes.add(replaceStrings); } //

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>Properties) { passes.add(convertToDottedProperties); } // Property renaming must happen before this pass runs since this // pass may convert dotted properties into quoted properties. It // is beneficial to run before alias strings, alias keywords and // variable renaming. if (options.rewriteFunctionExpressions) { passes.add(rewriteFunctionExpressions); } // This comes after converting quoted property accesses to dotted property // accesses in order to avoid aliasing property names. if (!options.aliasableStrings.isEmpty() || options.aliasAllStrings) { passes.add(aliasStrings); } if (options.aliasExternals) { passes.add(aliasExternals); } if (options.aliasKeywords) { passes.add(aliasKeywords); } // Passes after this point can no longer depend on normalized AST // assumptions. passes.add(markUnnormalized); if (options.coalesceVariableNames) { passes.add(coalesceVariableNames); // coalesceVariables creates identity assignments and more redundant code // that can be removed, rerun the peephole optimizations to clean them // up. if (options.foldConstants) { passes.add(peepholeOptimizations); } } if (options.collapseVariableDeclarations) { passes.add(exploitAssign); passes.add(collapseVariableDeclarations); } // This pass works best after collapseVariableDeclarations. passes.add(denormalize); if (options.instrumentationTemplate != null) { passes.add(instrumentFunctions); } if (options.variableRenaming != VariableRenamingPolicy.ALL) { // If we're leaving some (or all) variables with their old names, // then we need to undo any of the markers we added for distinguishing // local variables ("$$1"). passes.add(invertContextualRenaming); } if (options.variableRenaming != VariableRenamingPolicy.OFF) { passes.add(renameVars); } if (options.groupVariableDeclarations) { passes.add(groupVariableDeclarations); } // This pass should run after names stop changing. if (options.processObjectPropertyString) { passes.add(objectPropertyStringPostprocess); } if (options.labelRenaming) { passes.add(renameLabels); } if (options.foldConstants) { passes.add(latePeepholeOptimizations); } if (options.anonymousFunctionNaming == AnonymousFunctionNamingPolicy.UNMAPPED) { passes.add(nameUnmappedAnonymousFunctions); } if (options.renamePrefixNamespace != null) { if (!GLOBAL_SYMBOL_NAMESPACE_PATTERN.matcher( options.renamePrefixNamespace).matches()) { throw new IllegalArgumentException( "Illegal character in renamePrefixNamespace name: " + options.renamePrefixNamespace); } passes.add(rescopeGlobalSymbols); } passes.add(stripSideEffectProtection); // Safety checks passes.add(sanityCheckAst); passes.add(sanityCheckVars); return passes; } /** Creates the passes for the main optimization loop. */ private List<PassFactory> getMainOptimizationLoop() { List<PassFactory> passes = Lists.newArrayList(); if (options.inlineGetters) { passes.add(inlineSimpleMethods); } passes.addAll(getCodeRemovingPasses()); if (options.inlineFunctions || options.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>inlineLocalFunctions) { passes.add(inlineFunctions); } if (options.inlineProperties) { passes.add(inlineProperties); } boolean runOptimizeCalls = options.optimizeCalls || options.optimizeParameters || options.optimizeReturns; if (options.removeUnusedVars || options.removeUnusedLocalVars) { if (options.deadAssignmentElimination) { passes.add(deadAssignmentsElimination); } if (!runOptimizeCalls) { passes.add(removeUnusedVars); } } if (runOptimizeCalls) { passes.add(optimizeCallsAndRemoveUnusedVars); } assertAllLoopablePasses(passes); return passes; } /** Creates several passes aimed at removing code. */ private List<PassFactory> getCodeRemovingPasses() { List<PassFactory> passes = Lists.newArrayList(); if (options.collapseObjectLiterals && !isInliningForbidden()) { passes.add(collapseObjectLiterals); } if (options.inlineVariables || options.inlineLocalVariables) { passes.add(inlineVariables); } else if (options.inlineConstantVars) { passes.add(inlineConstants); } if (options.foldConstants) { // These used to be one pass. passes.add(minimizeExitPoints); passes.add(peepholeOptimizations); } if (options.removeDeadCode) { passes.add(removeUnreachableCode); } if (options.removeUnusedPrototypeProperties) { passes.add(removeUnusedPrototypeProperties); } if (options.removeUnusedClassProperties && !isInliningForbidden()) { passes.add(removeUnusedClassProperties); } assertAllLoopablePasses(passes); return passes; } /** * Checks for code that is probably wrong (such as stray expressions). */ final HotSwapPassFactory checkSideEffects = new HotSwapPassFactory("checkSideEffects", true) { @Override protected HotSwapCompilerPass createInternal(final AbstractCompiler compiler) { // The current approach to protecting "hidden" side-effects is to // wrap them in a function call that is stripped later, this shouldn't // be done in IDE mode where AST changes may be unexpected. boolean protectHiddenSideEffects = options.protectHiddenSideEffects && !options.ideMode; return new CheckSideEffects(compiler, options.checkSuspiciousCode ? CheckLevel.WARNING : CheckLevel.OFF, protectHiddenSideEffects); } }; /** * Checks for code that is probably wrong (such as stray expressions). */ final PassFactory stripSideEffectProtection = new PassFactory("stripSideEffectProtection", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CheckSideEffects.StripProtection(compiler); } }; /** * Checks for code that is probably wrong (such as stray expressions). */ // TODO(bolinfest): Write a CompilerPass for this. final HotSwapPassFactory suspiciousCode = new HotSwapPassFactory("suspiciousCode", true) { @Override protected HotSwapCompilerPass createInternal(final AbstractCompiler compiler) { List<Callback> sharedCallbacks = Lists.newArrayList(); if (options.checkSuspiciousCode) { sharedCallbacks.add(new CheckAccidentalSemicolon(CheckLevel.WARNING)); } if (options.enables(DiagnosticGroups

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>.GLOBAL_THIS)) { sharedCallbacks.add(new CheckGlobalThis(compiler)); } if (options.enables(DiagnosticGroups.DEBUGGER_STATEMENT_PRESENT)) { sharedCallbacks.add(new CheckDebuggerStatement(compiler)); } return combineChecks(compiler, sharedCallbacks); } }; /** Verify that all the passes are one-time passes. */ private void assertAllOneTimePasses(List<PassFactory> passes) { for (PassFactory pass : passes) { Preconditions.checkState(pass.isOneTimePass()); } } /** Verify that all the passes are multi-run passes. */ private void assertAllLoopablePasses(List<PassFactory> passes) { for (PassFactory pass : passes) { Preconditions.checkState(!pass.isOneTimePass()); } } /** Checks for validity of the control structures. */ final HotSwapPassFactory checkControlStructures = new HotSwapPassFactory("checkControlStructures", true) { @Override protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) { return new ControlStructureCheck(compiler); } }; /** Checks that all constructed classes are goog.require()d. */ final HotSwapPassFactory checkRequires = new HotSwapPassFactory("checkRequires", true) { @Override protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) { return new CheckRequiresForConstructors(compiler, options.checkRequires); } }; /** Makes sure @constructor is paired with goog.provides(). */ final HotSwapPassFactory checkProvides = new HotSwapPassFactory("checkProvides", true) { @Override protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) { return new CheckProvides(compiler, options.checkProvides); } }; private static final DiagnosticType GENERATE_EXPORTS_ERROR = DiagnosticType.error( "JSC_GENERATE_EXPORTS_ERROR", "Exports can only be generated if export symbol/property " + "functions are set."); /** Generates exports for @export annotations. */ final PassFactory generateExports = new PassFactory("generateExports", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { CodingConvention convention = compiler.getCodingConvention(); if (convention.getExportSymbolFunction() != null && convention.getExportPropertyFunction() != null) { return new GenerateExports(compiler, convention.getExportSymbolFunction(), convention.getExportPropertyFunction()); } else { return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR); } } }; /** Generates exports for functions associated with JsUnit. */ final PassFactory exportTestFunctions = new PassFactory("exportTestFunctions", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { CodingConvention convention = compiler.getCodingConvention(); if (convention.getExportSymbolFunction() != null) { return new ExportTestFunctions(compiler, convention.getExportSymbolFunction(), convention.getExportPropertyFunction()); } else { return new ErrorPass(compiler, GENERATE_EXPORTS_ERROR); } } }; /** Raw exports processing pass. */ final PassFactory gatherRawExports = new PassFactory("gatherRawExports", false) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { final GatherRawExports pass = new GatherRawExports( compiler); return new CompilerPass() { @Override

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> final HotSwapPassFactory resolveTypes = new HotSwapPassFactory("resolveTypes", false) { @Override protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) { return new GlobalTypeResolver(compiler); } }; /** Clears the typed scope when we're done. */ final PassFactory clearTypedScopePass = new PassFactory("clearTypedScopePass", false) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new ClearTypedScope(); } }; /** Runs type inference. */ final HotSwapPassFactory inferTypes = new HotSwapPassFactory("inferTypes", false) { @Override protected HotSwapCompilerPass createInternal(final AbstractCompiler compiler) { return new HotSwapCompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(getTypedScopeCreator()); makeTypeInference(compiler).process(externs, root); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { makeTypeInference(compiler).inferTypes(scriptRoot); } }; } }; final HotSwapPassFactory inferJsDocInfo = new HotSwapPassFactory("inferJsDocInfo", false) { @Override protected HotSwapCompilerPass createInternal( final AbstractCompiler compiler) { return new HotSwapCompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(getTypedScopeCreator()); makeInferJsDocInfo(compiler).process(externs, root); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { makeInferJsDocInfo(compiler).hotSwapScript(scriptRoot, originalRoot); } }; } }; /** Checks type usage */ final HotSwapPassFactory checkTypes = new HotSwapPassFactory("checkTypes", false) { @Override protected HotSwapCompilerPass createInternal(final AbstractCompiler compiler) { return new HotSwapCompilerPass() { @Override public void process(Node externs, Node root) { Preconditions.checkNotNull(topScope); Preconditions.checkNotNull(getTypedScopeCreator()); TypeCheck check = makeTypeCheck(compiler); check.process(externs, root); compiler.getErrorManager().setTypedPercent(check.getTypedPercent()); } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { makeTypeCheck(compiler).check(scriptRoot, false); } }; } }; /** * Checks possible execution paths of the program for problems: missing return * statements and dead code. */ final HotSwapPassFactory checkControlFlow = new HotSwapPassFactory("checkControlFlow", true) { @Override protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) { List<Callback> callbacks = Lists.newArrayList(); if (options.checkUnreachableCode.isOn()) { callbacks.add( new CheckUnreachableCode(compiler, options.checkUnreachableCode)); } if (options.checkMissingReturn.isOn() && options.checkTypes) { callbacks.add( new CheckMissingReturn(compiler, options.checkMissingReturn)); } return combineChecks(compiler, callbacks); } }; /** Checks access controls. Depends on type-inference. */ final HotSwapPassFactory checkAccessControls = new

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> HotSwapPassFactory("checkAccessControls", true) { @Override protected HotSwapCompilerPass createInternal(AbstractCompiler compiler) { return new CheckAccessControls(compiler); } }; /** Executes the given callbacks with a {@link CombinedCompilerPass}. */ private static HotSwapCompilerPass combineChecks(AbstractCompiler compiler, List<Callback> callbacks) { Preconditions.checkArgument(callbacks.size() > 0); Callback[] array = callbacks.toArray(new Callback[callbacks.size()]); return new CombinedCompilerPass(compiler, array); } /** A compiler pass that resolves types in the global scope. */ class GlobalTypeResolver implements HotSwapCompilerPass { private final AbstractCompiler compiler; GlobalTypeResolver(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { if (topScope == null) { regenerateGlobalTypedScope(compiler, root.getParent()); } else { compiler.getTypeRegistry().resolveTypesInScope(topScope); } } @Override public void hotSwapScript(Node scriptRoot, Node originalRoot) { patchGlobalTypedScope(compiler, scriptRoot); } } /** A compiler pass that clears the global scope. */ class ClearTypedScope implements CompilerPass { @Override public void process(Node externs, Node root) { clearTypedScope(); } } /** Checks global name usage. */ final PassFactory checkGlobalNames = new PassFactory("checkGlobalNames", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { // Create a global namespace for analysis by check passes. // Note that this class does all heavy computation lazily, // so it's OK to create it here. namespaceForChecks = new GlobalNamespace(compiler, externs, jsRoot); new CheckGlobalNames(compiler, options.checkGlobalNamesLevel) .injectNamespace(namespaceForChecks).process(externs, jsRoot); } }; } }; /** Checks that the code is ES5 or Caja compliant. */ final PassFactory checkStrictMode = new PassFactory("checkStrictMode", true) { @Override protected CompilerPass createInternal(AbstractCompiler compiler) { return new StrictModeCheck(compiler, !options.checkSymbols, // don't check variables twice !options.checkCaja); // disable eval check if not Caja } }; /** Process goog.tweak.getTweak() calls. */ final PassFactory processTweaks = new PassFactory("processTweaks", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { new ProcessTweaks(compiler, options.getTweakProcessing().shouldStrip(), options.getTweakReplacements()).process(externs, jsRoot); } }; } }; /** Override @define-annotated constants. */ final PassFactory processDefines = new PassFactory("processDefines", true) { @Override protected CompilerPass createInternal(final AbstractCompiler compiler) { return new CompilerPass() { @Override public void process(Node externs, Node jsRoot) { Map<String, Node> replacements = getAdditionalReplacements(options); replacement

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>able. * * @param name A global variable or function name. * @param local {@code true} if the name is a local variable. * @return {@code true} if the name should be considered exported. */ public boolean isExported(String name, boolean local); /** * Should be isExported(name, true) || isExported(name, false); */ public boolean isExported(String name); /** * Checks whether a name should be considered private. Private global * variables and functions can only be referenced within the source file in * which they are declared. Private properties and methods should only be * accessed by the class that defines them. * * @param name The name of a global variable or function, or a method or * property. * @return {@code true} if the name should be considered private. */ public boolean isPrivate(String name); /** * Checks if the given method defines a subclass relationship, * and if it does, returns information on that relationship. By default, * always returns null. Meant to be overridden by subclasses. * * @param callNode A CALL node. */ public SubclassRelationship getClassesDefinedByCall(Node callNode); /** * Returns true if passed a string referring to the superclass. The string * will usually be from the string node at the right of a GETPROP, e.g. * this.superClass_. */ public boolean isSuperClassReference(String propertyName); /** * Convenience method for determining provided dependencies amongst different * JS scripts. */ public String extractClassNameIfProvide(Node node, Node parent); /** * Convenience method for determining required dependencies amongst different * JS scripts. */ public String extractClassNameIfRequire(Node node, Node parent); /** * Function name used when exporting properties. * Signature: fn(object, publicName, symbol). * @return function name. */ public String getExportPropertyFunction(); /** * Function name used when exporting symbols. * Signature: fn(publicPath, object). * @return function name. */ public String getExportSymbolFunction(); /** * Checks if the given CALL node is forward-declaring any types, * and returns the name of the types if it is. */ public List<String> identifyTypeDeclarationCall(Node n); /** * In many JS libraries, the function that produces inheritance also * adds properties to the superclass and/or subclass. */ public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type); /** * Function name for abstract methods. An abstract method can be assigned to * an interface method instead of an function expression in order to avoid * linter warnings produced by assigning a function without a return value * where a return value is expected. * @return function name. */ public String getAbstractMethodName(); /** * Checks if the given method defines a singleton getter, and if it does, * returns the name of the class with the singleton getter. By default, always * returns null. Meant to be overridden by subclasses. * * @param callNode A CALL node. */ public String getSingletonGetterClassName(Node callNode); /** * In many JS libraries, the function that adds a singleton getter to a class * adds properties to the class. */ public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType); /**

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> * @return Whether the function is inlinable by convention. */ public boolean isInlinableFunction(Node n); /** * @return the delegate relationship created by the call or null. */ public DelegateRelationship getDelegateRelationship(Node callNode); /** * In many JS libraries, the function that creates a delegate relationship * also adds properties to the delegator and delegate base. */ public void applyDelegateRelationship( ObjectType delegateSuperclass, ObjectType delegateBase, ObjectType delegator, FunctionType delegateProxy, FunctionType findDelegate); /** * @return the name of the delegate superclass. */ public String getDelegateSuperclassName(); /** * Checks for function calls that set the calling conventions on delegate * methods. */ public void checkForCallingConventionDefiningCalls( Node n, Map<String, String> delegateCallingConventions); /** * Defines the delegate proxy prototype properties. Their types depend on * properties of the delegate base methods. * * @param delegateProxyPrototypes List of delegate proxy prototypes. */ public void defineDelegateProxyPrototypeProperties( JSTypeRegistry registry, StaticScope<JSType> scope, List<ObjectType> delegateProxyPrototypes, Map<String, String> delegateCallingConventions); /** * Gets the name of the global object. */ public String getGlobalObject(); /** * A Bind instance or null. */ public Bind describeFunctionBind(Node n); /** * A Bind instance or null. * @param useTypeInfo If we believe type information is reliable enough * to use to figure out what the bind function is. */ public Bind describeFunctionBind(Node n, boolean useTypeInfo); public static class Bind { // The target of the bind action final Node target; // The node representing the "this" value, maybe null final Node thisValue; // The head of a Node list representing the parameters final Node parameters; public Bind(Node target, Node thisValue, Node parameters) { this.target = target; this.thisValue = thisValue; this.parameters = parameters; } /** * The number of parameters bound (not including the 'this' value). */ int getBoundParameterCount() { if (parameters == null) { return 0; } Node paramParent = parameters.getParent(); return paramParent.getChildCount() - paramParent.getIndexOfChild(parameters); } } /** * Whether this CALL function is testing for the existence of a property. */ public boolean isPropertyTestFunction(Node call); /** * Whether this GETPROP node is an alias for an object prototype. */ public boolean isPrototypeAlias(Node getProp); /** * Checks if the given method performs a object literal cast, and if it does, * returns information on the cast. By default, always returns null. Meant * to be overridden by subclasses. * * @param callNode A CALL node. */ public ObjectLiteralCast getObjectLiteralCast(Node callNode); /** * Gets a collection of all properties that are defined indirectly on global * objects. (For example, Closure defines superClass_ in the goog.inherits * call). */ public Collection<String> getIndirectlyDeclaredProperties(); /** * Returns the set of AssertionFunction. */ public Collection<AssertionFunctionSpec> getAssertionFunctions(); static enum SubclassType { INHERITS, MIXIN } static class

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>; } return this; } @Override public String toDebugHashCodeString() { List<String> hashCodes = Lists.newArrayList(); for (JSType a : alternates) { hashCodes.add(a.toDebugHashCodeString()); } return "{(" + Joiner.on(",").join(hashCodes) + ")}"; } @Override public boolean setValidator(Predicate<JSType> validator) { for (JSType a : alternates) { a.setValidator(validator); } return true; } @Override public JSType collapseUnion() { JSType currentValue = null; ObjectType currentCommonSuper = null; for (JSType a : alternates) { if (a.isUnknownType()) { return getNativeType(JSTypeNative.UNKNOWN_TYPE); } ObjectType obj = a.toObjectType(); if (obj == null) { if (currentValue == null && currentCommonSuper == null) { // If obj is not an object, then it must be a value. currentValue = a; } else { // Multiple values and objects will always collapse to the ALL_TYPE. return getNativeType(JSTypeNative.ALL_TYPE); } } else if (currentValue != null) { // Values and objects will always collapse to the ALL_TYPE. return getNativeType(JSTypeNative.ALL_TYPE); } else if (currentCommonSuper == null) { currentCommonSuper = obj; } else { currentCommonSuper = registry.findCommonSuperObject(currentCommonSuper, obj); } } return currentCommonSuper; } @Override public void matchConstraint(JSType constraint) { for (JSType alternate : alternates) { alternate.matchConstraint(constraint); } } @Override public boolean hasAnyTemplateInternal() { for (JSType alternate : alternates) { if (alternate.hasAnyTemplate()) { return true; } } return false; } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> charno) { errorReporter.warning( "Bad type annotation. " + ScriptRuntime.getMessage0(messageId), getSourceName(), lineno, null, charno); } } // The DocInfo with the fileoverview tag for the whole file. private JSDocInfo fileOverviewJSDocInfo = null; private State state; private final Map<String, Annotation> annotationNames; private final Set<String> suppressionNames; static private final Set<String> modifiesAnnotationKeywords = ImmutableSet.<String>of("this", "arguments"); private Node.FileLevelJsDocBuilder fileLevelJsDocBuilder; /** * Sets the JsDocBuilder for the file-level (root) node of this parse. The * parser uses the builder to append any preserve annotations it encounters * in JsDoc comments. * * @param fileLevelJsDocBuilder */ void setFileLevelJsDocBuilder( Node.FileLevelJsDocBuilder fileLevelJsDocBuilder) { this.fileLevelJsDocBuilder = fileLevelJsDocBuilder; } /** * Sets the file overview JSDocInfo, in order to warn about multiple uses of * the @fileoverview tag in a file. */ void setFileOverviewJSDocInfo(JSDocInfo fileOverviewJSDocInfo) { this.fileOverviewJSDocInfo = fileOverviewJSDocInfo; } private enum State { SEARCHING_ANNOTATION, SEARCHING_NEWLINE, NEXT_IS_ANNOTATION } JsDocInfoParser(JsDocTokenStream stream, Comment commentNode, Node associatedNode, Config config, ErrorReporter errorReporter) { this.stream = stream; this.associatedNode = associatedNode; // Sometimes this will be null in tests. this.sourceFile = associatedNode == null ? null : associatedNode.getStaticSourceFile(); this.jsdocBuilder = new JSDocInfoBuilder(config.parseJsDocDocumentation); if (commentNode != null) { this.jsdocBuilder.recordOriginalCommentString(commentNode.getValue()); } this.annotationNames = config.annotationNames; this.suppressionNames = config.suppressionNames; this.errorReporter = errorReporter; this.templateNode = this.createTemplateNode(); } private String getSourceName() { return sourceFile == null ? null : sourceFile.getName(); } /** * Parses a string containing a JsDoc type declaration, returning the * type if the parsing succeeded or {@code null} if it failed. */ public static Node parseTypeString(String typeString) { Config config = new Config( Sets.<String>newHashSet(), Sets.<String>newHashSet(), false, LanguageMode.ECMASCRIPT3, false); JsDocInfoParser parser = new JsDocInfoParser( new JsDocTokenStream(typeString), null, null, config, NullErrorReporter.forNewRhino()); return parser.parseTopLevelTypeExpression(parser.next()); } /** * Parses a {@link JSDocInfo} object. This parsing method reads all tokens * returned by the {@link JsDocTokenStream#getJsDocToken()} method until the * {@link JsDocToken#EOC} is returned. * * @return {@code true} if JSDoc information was correctly parsed, * {@code false} otherwise */ boolean parse

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>() { int lineno; int charno; // JSTypes are represented as Rhino AST nodes, and then resolved later. JSTypeExpression type; state = State.SEARCHING_ANNOTATION; skipEOLs(); JsDocToken token = next(); List<ExtendedTypeInfo> extendedTypes = Lists.newArrayList(); // Always record that we have a comment. if (jsdocBuilder.shouldParseDocumentation()) { ExtractionInfo blockInfo = extractBlockComment(token); token = blockInfo.token; if (!blockInfo.string.isEmpty()) { jsdocBuilder.recordBlockDescription(blockInfo.string); } } else { if (token != JsDocToken.ANNOTATION && token != JsDocToken.EOC) { // Mark that there was a description, but don't bother marking // what it was. jsdocBuilder.recordBlockDescription(""); } } // Parse the actual JsDoc. retry: for (;;) { switch (token) { case ANNOTATION: if (state == State.SEARCHING_ANNOTATION) { state = State.SEARCHING_NEWLINE; lineno = stream.getLineno(); charno = stream.getCharno(); String annotationName = stream.getString(); Annotation annotation = annotationNames.get(annotationName); if (annotation == null) { parser.addParserWarning("msg.bad.jsdoc.tag", annotationName, stream.getLineno(), stream.getCharno()); } else { // Mark the beginning of the annotation. jsdocBuilder.markAnnotation(annotationName, lineno, charno); switch (annotation) { case AUTHOR: if (jsdocBuilder.shouldParseDocumentation()) { ExtractionInfo authorInfo = extractSingleLineBlock(); String author = authorInfo.string; if (author.length() == 0) { parser.addParserWarning("msg.jsdoc.authormissing", stream.getLineno(), stream.getCharno()); } else { jsdocBuilder.addAuthor(author); } token = authorInfo.token; } else { token = eatTokensUntilEOL(token); } continue retry; case CONSISTENTIDGENERATOR: if (!jsdocBuilder.recordConsistentIdGenerator()) { parser.addParserWarning("msg.jsdoc.consistidgen", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case CONSTANT: if (!jsdocBuilder.recordConstancy()) { parser.addParserWarning("msg.jsdoc.const", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case CONSTRUCTOR: if (!jsdocBuilder.recordConstructor()) { if (jsdocBuilder.isInterfaceRecorded()) { parser.addTypeWarning("msg.jsdoc.interface.constructor", stream.getLineno(), stream.getCharno()); } else { parser.addTypeWarning("msg.jsdoc.incompat.type", stream.getLineno(), stream.getCharno()); } } token = eatTokensUntilEOL(); continue retry; case DEPRECATED: if (!jsdocBuilder.recordDeprecated()) { parser.addParserWarning("msg.jsdoc.deprecated", stream.getLineno(), stream.getCharno()); } // Find

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>("msg.jsdoc.visibility.public", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case NO_SHADOW: if (!jsdocBuilder.recordNoShadow()) { parser.addParserWarning("msg.jsdoc.noshadow", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case NO_SIDE_EFFECTS: if (!jsdocBuilder.recordNoSideEffects()) { parser.addParserWarning("msg.jsdoc.nosideeffects", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case MODIFIES: token = parseModifiesTag(next()); continue retry; case IMPLICIT_CAST: if (!jsdocBuilder.recordImplicitCast()) { parser.addTypeWarning("msg.jsdoc.implicitcast", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case SEE: if (jsdocBuilder.shouldParseDocumentation()) { ExtractionInfo referenceInfo = extractSingleLineBlock(); String reference = referenceInfo.string; if (reference.length() == 0) { parser.addParserWarning("msg.jsdoc.seemissing", stream.getLineno(), stream.getCharno()); } else { jsdocBuilder.addReference(reference); } token = referenceInfo.token; } else { token = eatTokensUntilEOL(token); } continue retry; case SUPPRESS: token = parseSuppressTag(next()); continue retry; case TEMPLATE: ExtractionInfo templateInfo = extractSingleLineBlock(); List<String> names = Lists.newArrayList( Splitter.on(',') .trimResults() .split(templateInfo.string)); if (names.size() == 0 || names.get(0).length() == 0) { parser.addTypeWarning("msg.jsdoc.templatemissing", stream.getLineno(), stream.getCharno()); } else if (!jsdocBuilder.recordTemplateTypeNames(names)) { parser.addTypeWarning("msg.jsdoc.template.at.most.once", stream.getLineno(), stream.getCharno()); } token = templateInfo.token; continue retry; case IDGENERATOR: if (!jsdocBuilder.recordIdGenerator()) { parser.addParserWarning("msg.jsdoc.idgen", stream.getLineno(), stream.getCharno()); } token = eatTokensUntilEOL(); continue retry; case VERSION: ExtractionInfo versionInfo = extractSingleLineBlock(); String version = versionInfo.string; if (version.length() == 0) { parser.addParserWarning("msg.jsdoc.versionmissing", stream.getLineno(), stream.getCharno()); } else { if (!jsdocBuilder.recordVersion(version)) { parser.addParserWarning("msg.jsdoc.extraversion", stream.getLineno(), stream.getCharno()); } } token = versionInfo.token; continue retry; case DEFINE: case RETURN: case THIS: case TYPE: case TYPEDEF: lineno = stream.getLineno(); charno = stream.getCharno

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>(); } } private void checkExtendedTypes(List<ExtendedTypeInfo> extendedTypes) { for (ExtendedTypeInfo typeInfo : extendedTypes) { // If interface, record the multiple extended interfaces if (jsdocBuilder.isInterfaceRecorded()) { if (!jsdocBuilder.recordExtendedInterface(typeInfo.type)) { parser.addParserWarning("msg.jsdoc.extends.duplicate", typeInfo.lineno, typeInfo.charno); } } else { if (!jsdocBuilder.recordBaseType(typeInfo.type)) { parser.addTypeWarning("msg.jsdoc.incompat.type", typeInfo.lineno, typeInfo.charno); } } } } /** * Parse a {@code @suppress} tag of the form * {@code @suppress&#123;warning1|warning2&#125;}. * * @param token The current token. */ private JsDocToken parseSuppressTag(JsDocToken token) { if (token == JsDocToken.LC) { Set<String> suppressions = new HashSet<String>(); while (true) { if (match(JsDocToken.STRING)) { String name = stream.getString(); if (!suppressionNames.contains(name)) { parser.addParserWarning("msg.jsdoc.suppress.unknown", name, stream.getLineno(), stream.getCharno()); } suppressions.add(stream.getString()); token = next(); } else { parser.addParserWarning("msg.jsdoc.suppress", stream.getLineno(), stream.getCharno()); return token; } if (match(JsDocToken.PIPE)) { token = next(); } else { break; } } if (!match(JsDocToken.RC)) { parser.addParserWarning("msg.jsdoc.suppress", stream.getLineno(), stream.getCharno()); } else { token = next(); if (!jsdocBuilder.recordSuppressions(suppressions)) { parser.addParserWarning("msg.jsdoc.suppress.duplicate", stream.getLineno(), stream.getCharno()); } } } return token; } /** * Parse a {@code @modifies} tag of the form * {@code @modifies&#123;this|arguments|param&#125;}. * * @param token The current token. */ private JsDocToken parseModifiesTag(JsDocToken token) { if (token == JsDocToken.LC) { Set<String> modifies = new HashSet<String>(); while (true) { if (match(JsDocToken.STRING)) { String name = stream.getString(); if (!modifiesAnnotationKeywords.contains(name) && !jsdocBuilder.hasParameter(name)) { parser.addParserWarning("msg.jsdoc.modifies.unknown", name, stream.getLineno(), stream.getCharno()); } modifies.add(stream.getString()); token = next(); } else { parser.addParserWarning("msg.jsdoc.modifies", stream.getLineno(), stream.getCharno()); return token; } if (match(JsDocToken.PIPE)) { token = next(); } else { break; } } if (!match(JsDocToken.RC))

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> return true; default: return false; } } /** * Get the value of the @nosideeffects annotation stored in the * doc info. */ private static boolean hasNoSideEffectsAnnotation(Node node) { JSDocInfo docInfo = node.getJSDocInfo(); return docInfo != null && docInfo.isNoSideEffects(); } /** * Gather function nodes that have @nosideeffects annotations. */ private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback { private final boolean inExterns; GatherNoSideEffectFunctions(boolean inExterns) { this.inExterns = inExterns; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (!inExterns && hasNoSideEffectsAnnotation(node)) { traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION); } if (node.isGetProp()) { if (parent.isExprResult() && hasNoSideEffectsAnnotation(node)) { noSideEffectFunctionNames.add(node); } } else if (node.isFunction()) { // The annotation may attached to the function node, the // variable declaration or assignment expression. boolean hasAnnotation = hasNoSideEffectsAnnotation(node); List<Node> nameNodes = Lists.newArrayList(); nameNodes.add(node.getFirstChild()); Node nameNode = null; if (parent.isName()) { Node gramp = parent.getParent(); if (gramp.isVar() && gramp.hasOneChild() && hasNoSideEffectsAnnotation(gramp)) { hasAnnotation = true; } nameNodes.add(parent); } else if (parent.isAssign()) { if (hasNoSideEffectsAnnotation(parent)) { hasAnnotation = true; } nameNodes.add(parent.getFirstChild()); } if (hasAnnotation) { noSideEffectFunctionNames.addAll(nameNodes); } } } } /** * Set the no side effects property for CALL and NEW nodes that * refer to function names that are known to have no side effects. */ private class SetNoSideEffectCallProperty extends AbstractPostOrderCallback { private final SimpleDefinitionFinder defFinder; SetNoSideEffectCallProperty(SimpleDefinitionFinder defFinder) { this.defFinder = defFinder; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { if (!node.isCall() && !node.isNew()) { return; } Collection<Definition> definitions = defFinder.getDefinitionsReferencedAt(node.getFirstChild()); if (definitions == null) { return; } for (Definition def : definitions) { Node lValue = def.getLValue(); Preconditions.checkNotNull(lValue); if (!noSideEffectFunctionNames.contains(lValue) && definitionTypeContainsFunctionType(def)) { return; } } node.setSideEffectFlags(Node.NO_SIDE_EFFECTS); } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>(useSite); if (name != null) { Collection<Definition> defs = nameDefinitionMultimap.get(name); if (!defs.isEmpty()) { return defs; } else { return null; } } else { return null; } } @Override public void process(Node externs, Node source) { NodeTraversal.traverse( compiler, externs, new DefinitionGatheringCallback(true)); NodeTraversal.traverse( compiler, source, new DefinitionGatheringCallback(false)); NodeTraversal.traverse( compiler, source, new UseSiteGatheringCallback()); } /** * Returns a collection of use sites that may refer to provided * definition. Returns an empty collection if the definition is not * used anywhere. * * @param definition Definition of interest. * @return use site collection. */ Collection<UseSite> getUseSites(Definition definition) { String name = getSimplifiedName(definition.getLValue()); return nameUseSiteMultimap.get(name); } /** * Extract a name from a node. In the case of GETPROP nodes, * replace the namespace or object expression with "this" for * simplicity and correctness at the expense of inefficiencies due * to higher chances of name collisions. * * TODO(user) revisit. it would be helpful to at least use fully * qualified names in the case of namespaces. Might not matter as * much if this pass runs after "collapsing properties". */ private static String getSimplifiedName(Node node) { if (node.isName()) { String name = node.getString(); if (name != null && !name.isEmpty()) { return name; } else { return null; } } else if (node.isGetProp()) { return "this." + node.getLastChild().getString(); } return null; } private class DefinitionGatheringCallback extends AbstractPostOrderCallback { private boolean inExterns; DefinitionGatheringCallback(boolean inExterns) { this.inExterns = inExterns; } @Override public void visit(NodeTraversal traversal, Node node, Node parent) { // Arguments of external functions should not count as name // definitions. They are placeholder names for documentation // purposes only which are not reachable from anywhere. if (inExterns && node.isName() && parent.isParamList()) { return; } Definition def = DefinitionsRemover.getDefinition(node, inExterns); if (def != null) { String name = getSimplifiedName(def.getLValue()); if (name != null) { Node rValue = def.getRValue(); if ((rValue != null) && !NodeUtil.isImmutableValue(rValue) && !rValue.isFunction()) { // Unhandled complex expression Definition unknownDef = new UnknownDefinition(def.getLValue(), inExterns); def = unknownDef; } // TODO(johnlenz) : remove this stub dropping code if it becomes // illegal to have untyped stubs in the externs definitions. if (inExterns) { // We need special handling of untyped externs stubs here: // the stub should be dropped if the name is provided elsewhere. List<Definition> stubsToRemove = Lists.newArrayList(); String qualifiedName = node.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> private Deque<GraphAnnotationState> nodeAnnotationStack; /** * Used by {@link #pushEdgeAnnotations()} and {@link #popEdgeAnnotations()}. */ private Deque<GraphAnnotationState> edgeAnnotationStack; /** * Connects two nodes in the graph with an edge. * * @param n1 First node. * @param edge The edge. * @param n2 Second node. */ public abstract void connect(N n1, E edge, N n2); /** * Disconnects two nodes in the graph by removing all edges between them. * * @param n1 First node. * @param n2 Second node. */ public abstract void disconnect(N n1, N n2); /** * Connects two nodes in the graph with an edge if such edge does not already * exists between the nodes. * * @param n1 First node. * @param edge The edge. * @param n2 Second node. */ public final void connectIfNotFound(N n1, E edge, N n2) { if (!isConnected(n1, edge, n2)) { connect(n1, edge, n2); } } /** * Gets a node from the graph given a value. New nodes are created if that * value has not been assigned a graph node. Values equality are compared * using <code>Object.equals</code>. * * @param value The node's value. * @return The corresponding node in the graph. */ public abstract GraphNode<N, E> createNode(N value); /** Gets an immutable list of all nodes. */ @Override public abstract Collection<GraphNode<N, E>> getNodes(); /** Gets an immutable list of all edges. */ public abstract List<GraphEdge<N, E>> getEdges(); /** * Gets the degree of a node. * * @param value The node's value. * @return The degree of the node. */ public abstract int getNodeDegree(N value); @Override public int getWeight(N value) { return getNodeDegree(value); } /** * Gets the neighboring nodes. * * @param value The node's value. * @return A list of neighboring nodes. */ public abstract List<GraphNode<N, E>> getNeighborNodes(N value); public abstract Iterator<GraphNode<N, E>> getNeighborNodesIterator(N value); /** * Retrieves an edge from the graph. * * @param n1 Node one. * @param n2 Node two. * @return The list of edges between those two values in the graph. */ public abstract List<GraphEdge<N, E>> getEdges(N n1, N n2); /** * Retrieves any edge from the graph. * * @param n1 Node one. * @param n2 Node two. * @return The first edges between those two values in the graph. null if * there are none. */ public abstract GraphEdge<N, E> getFirstEdge(N n1, N n2); /** * Checks whether the node exists in the graph ({@link #createNode(Object)} * has been called with that value). * * @param n Node. * @return <code>true</code> if it exist. */ public final boolean hasNode(N n) {

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> return getNode(n) != null; } /** * Checks whether two nodes in the graph are connected. * * @param n1 Node 1. * @param n2 Node 2. * @return <code>true</code> if the two nodes are connected. */ public abstract boolean isConnected(N n1, N n2); /** * Checks whether two nodes in the graph are connected by the given * edge type. * * @param n1 Node 1. * @param e The edge type. * @param n2 Node 2. */ public abstract boolean isConnected(N n1, E e, N n2); /** * Gets the node of the specified type, or throws an * IllegalArgumentException. */ @SuppressWarnings("unchecked") <T extends GraphNode<N, E>> T getNodeOrFail(N val) { T node = (T) getNode(val); if (node == null) { throw new IllegalArgumentException(val + " does not exist in graph"); } return node; } @Override public final void clearNodeAnnotations() { for (GraphNode<N, E> n : getNodes()) { n.setAnnotation(null); } } /** Makes each edge's annotation null. */ public final void clearEdgeAnnotations() { for (GraphEdge<N, E> e : getEdges()) { e.setAnnotation(null); } } /** * Pushes nodes' annotation values. Restored with * {@link #popNodeAnnotations()}. Nodes' annotation values are cleared. */ public final void pushNodeAnnotations() { if (nodeAnnotationStack == null) { nodeAnnotationStack = Lists.newLinkedList(); } pushAnnotations(nodeAnnotationStack, getNodes()); } /** * Restores nodes' annotation values to state before last * {@link #pushNodeAnnotations()}. */ public final void popNodeAnnotations() { Preconditions.checkNotNull(nodeAnnotationStack, "Popping node annotations without pushing."); popAnnotations(nodeAnnotationStack); } /** * Pushes edges' annotation values. Restored with * {@link #popEdgeAnnotations()}. Edges' annotation values are cleared. */ public final void pushEdgeAnnotations() { if (edgeAnnotationStack == null) { edgeAnnotationStack = Lists.newLinkedList(); } pushAnnotations(edgeAnnotationStack, getEdges()); } /** * Restores edges' annotation values to state before last * {@link #pushEdgeAnnotations()}. */ public final void popEdgeAnnotations() { Preconditions.checkNotNull(edgeAnnotationStack, "Popping edge annotations without pushing."); popAnnotations(edgeAnnotationStack); } /** * A generic edge. * * @param <N> Value type that the graph node stores. * @param <E> Value type that the graph edge stores. */ public interface GraphEdge<N, E> extends Annotatable { /** * Retrieves the edge's value. * * @return The value. */ E getValue(); GraphNode<N, E> getNodeA(); GraphNode<N, E> getNodeB(); } /** * A simple implementation of SubGraph that calculates adjacency by iterating * over a node's neighbors. */ class SimpleSubGraph<N, E> implements SubGraph<N, E> { private Graph<N, E> graph; private List<GraphNode<N, E

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>>> nodes = Lists.newArrayList(); SimpleSubGraph(Graph<N, E> graph) { this.graph = graph; } @Override public boolean isIndependentOf(N value) { GraphNode<N, E> node = graph.getNode(value); for (GraphNode<N, E> n : nodes) { if (graph.getNeighborNodes(n.getValue()).contains(node)) { return false; } } return true; } @Override public void addNode(N value) { nodes.add(graph.getNodeOrFail(value)); } } /** * Pushes a new list on stack and stores nodes annotations in the new list. * Clears objects' annotations as well. */ private static void pushAnnotations( Deque<GraphAnnotationState> stack, Collection<? extends Annotatable> haveAnnotations) { stack.push(new GraphAnnotationState(haveAnnotations.size())); for (Annotatable h : haveAnnotations) { stack.peek().add(new AnnotationState(h, h.getAnnotation())); h.setAnnotation(null); } } /** * Restores the node annotations on the top of stack and pops stack. */ private static void popAnnotations(Deque<GraphAnnotationState> stack) { for (AnnotationState as : stack.pop()) { as.first.setAnnotation(as.second); } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Predicate; import com.google.javascript.jscomp.ControlFlowGraph.Branch; import com.google.javascript.jscomp.NodeTraversal.ScopedCallback; import com.google.javascript.jscomp.graph.GraphNode; import com.google.javascript.jscomp.graph.GraphReachability; import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.TernaryValue; /** * Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user * about unreachable code. * */ class CheckUnreachableCode implements ScopedCallback { static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error( "JSC_UNREACHABLE_CODE", "unreachable code"); private final AbstractCompiler compiler; private final CheckLevel level; CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) { this.compiler = compiler; this.level = level; } @Override public void enterScope(NodeTraversal t) { initScope(t.getControlFlowGraph()); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n); if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) { // Only report error when there are some line number informations. // There are synthetic nodes with no line number informations, nodes // introduce by other passes (although not likely since this pass should // be executed early) or some rhino bug. if (n.getLineno() != -1 && // Allow spurious semi-colons and spurious breaks. !n.isEmpty() && !n.isBreak()) { compiler.report(t.makeError(n, level, UNREACHABLE_CODE)); // From now on, we are going to assume the user fixed the error and not // give more warning related to code section reachable from this node. new GraphReachability<Node, ControlFlowGraph.Branch>( t.getControlFlowGraph()).recompute(n); // Saves time by not traversing children. return false; } } return true; } private void initScope(ControlFlowGraph<Node> controlFlowGraph) { new GraphReachability<Node, ControlFlowGraph.Branch>( controlFlowGraph, new ReachablePredicate()).compute( controlFlowGraph.getEntry().getValue()); } @Override public void exitScope(NodeTraversal t) { }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> @Override public void visit(NodeTraversal t, Node n, Node parent) { } private final class ReachablePredicate implements Predicate<EdgeTuple<Node, ControlFlowGraph.Branch>> { @Override public boolean apply(EdgeTuple<Node, Branch> input) { Branch branch = input.edge; if (!branch.isConditional()) { return true; } Node predecessor = input.sourceNode; Node condition = NodeUtil.getConditionExpression(predecessor); // TODO(user): Handle more complicated expression like true == true, // etc.... if (condition != null) { TernaryValue val = NodeUtil.getImpureBooleanValue(condition); if (val != TernaryValue.UNKNOWN) { return val.toBoolean(true) == (branch == Branch.ON_TRUE); } } return true; } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> It captures a product * lattice for each local (non-escaped) variable. The sub-lattice is * a n + 2 element lattice with all the {@link Definition} in the program, * TOP and BOTTOM. * * <p>Since this is a Must-Define analysis, BOTTOM represents the case where * there might be more than one reaching definition for the variable. * * * (TOP) * / | | \ * N1 N2 N3 ....Nn * \ | | / * (BOTTOM) * */ static final class MustDef implements LatticeElement { // TODO(user): Use bit vector instead of maps might get better // performance. Change it after this is tested to be fully functional. // When a Var "A" = "TOP", "A" does not exist in reachingDef's keySet. // When a Var "A" = Node N, "A" maps to that node. // When a Var "A" = "BOTTOM", "A" maps to null. final Map<Var, Definition> reachingDef; public MustDef() { reachingDef = Maps.newHashMap(); } public MustDef(Iterator<Var> vars) { this(); while(vars.hasNext()) { Var var = vars.next(); // Every variable in the scope is defined once in the beginning of the // function: all the declared variables are undefined, all functions // have been assigned and all arguments has its value from the caller. reachingDef.put(var, new Definition(var.scope.getRootNode())); } } /** * Copy constructor. * * @param other The constructed object is a replicated copy of this element. */ public MustDef(MustDef other) { reachingDef = Maps.newHashMap(other.reachingDef); } @Override public boolean equals(Object other) { return (other instanceof MustDef) && ((MustDef) other).reachingDef.equals(this.reachingDef); } } private static class MustDefJoin extends JoinOp.BinaryJoinOp<MustDef> { @Override public MustDef apply(MustDef a, MustDef b) { MustDef result = new MustDef(); Map<Var, Definition> resultMap = result.reachingDef; // Take the join of all variables that are not TOP in this. for (Map.Entry<Var, Definition> varEntry : a.reachingDef.entrySet()) { Var var = varEntry.getKey(); Definition aDef = varEntry.getValue(); if (aDef == null) { // "a" is BOTTOM implies that the variable has more than one possible // definition. We set the join of this to be BOTTOM regardless of what // "b" might be. resultMap.put(var, null); continue; } Node aNode = aDef.node; if (b.reachingDef.containsKey(var)) { Definition bDef = b.reachingDef.get(var); if (aDef.equals(bDef)) { resultMap.put(var, aDef); } else { resultMap.put(var, null); } } else { resultMap.put(var, aDef); } } // Take the join of all variables that are not TOP in other but it is TOP // in this.

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> for (Map.Entry<Var, Definition> entry : b.reachingDef.entrySet()) { Var var = entry.getKey(); if (!a.reachingDef.containsKey(var)) { resultMap.put(var, entry.getValue()); } } return result; } } @Override boolean isForward() { return true; } @Override MustDef createEntryLattice() { return new MustDef(jsScope.getVars()); } @Override MustDef createInitialEstimateLattice() { return new MustDef(); } @Override MustDef flowThrough(Node n, MustDef input) { // TODO(user): We are doing a straight copy from input to output. There // might be some opportunities to cut down overhead. MustDef output = new MustDef(input); // TODO(user): This must know about ON_EX edges but it should handle // it better than what we did in liveness. Because we are in a forward mode, // we can used the branched forward analysis. computeMustDef(n, n, output, false); return output; } /** * @param n The node in question. * @param cfgNode The node to add * @param conditional true if the definition is not always executed. */ private void computeMustDef( Node n, Node cfgNode, MustDef output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.WHILE: case Token.DO: case Token.IF: computeMustDef( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); return; case Token.FOR: if (!NodeUtil.isForIn(n)) { computeMustDef( NodeUtil.getConditionExpression(n), cfgNode, output, conditional); } else { // for(x in y) {...} Node lhs = n.getFirstChild(); Node rhs = lhs.getNext(); if (lhs.isVar()) { lhs = lhs.getLastChild(); // for(var x in y) {...} } if (lhs.isName()) { addToDefIfLocal(lhs.getString(), cfgNode, rhs, output); } } return; case Token.AND: case Token.OR: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.HOOK: computeMustDef(n.getFirstChild(), cfgNode, output, conditional); computeMustDef(n.getFirstChild().getNext(), cfgNode, output, true); computeMustDef(n.getLastChild(), cfgNode, output, true); return; case Token.VAR: for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (c.hasChildren()) { computeMustDef(c.getFirstChild(), cfgNode, output, conditional); addToDefIfLocal(c.getString(), conditional ? null : cfgNode, c.getFirstChild(), output); } } return; default: if (NodeUtil.isAssignmentOp(n)) { if (n.getFirstChild().isName()) { Node name = n.getFirstChild(); computeMustDef(name.getNext(), cfgNode, output, conditional); addToDefIfLocal(

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>name.getString(), conditional ? null : cfgNode, n.getLastChild(), output); return; } else if (NodeUtil.isGet(n.getFirstChild())) { // Treat all assignments to arguments as redefining the // parameters itself. Node obj = n.getFirstChild().getFirstChild(); if (obj.isName() && "arguments".equals(obj.getString())) { // TODO(user): More accuracy can be introduced // i.e. We know exactly what arguments[x] is if x is a constant // number. escapeParameters(output); } } } if (n.isName() && "arguments".equals(n.getString())) { escapeParameters(output); } // DEC and INC actually defines the variable. if (n.isDec() || n.isInc()) { Node target = n.getFirstChild(); if (target.isName()) { addToDefIfLocal(target.getString(), conditional ? null : cfgNode, null, output); return; } } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { computeMustDef(c, cfgNode, output, conditional); } } } /** * Set the variable lattice for the given name to the node value in the def * lattice. Do nothing if the variable name is one of the escaped variable. * * @param node The CFG node where the definition should be record to. * {@code null} if this is a conditional define. */ private void addToDefIfLocal( String name, @Nullable Node node, @Nullable Node rValue, MustDef def) { Var var = jsScope.getVar(name); // var might be null because the variable might be defined in the extern // that we might not traverse. if (var == null || var.scope != jsScope) { return; } for (Var other : def.reachingDef.keySet()) { Definition otherDef = def.reachingDef.get(other); if (otherDef == null) { continue; } if (otherDef.depends.contains(var)) { def.reachingDef.put(other, null); } } if (!escaped.contains(var)) { if (node == null) { def.reachingDef.put(var, null); } else { Definition definition = new Definition(node); if (rValue != null) { computeDependence(definition, rValue); } def.reachingDef.put(var, definition); } } } private void escapeParameters(MustDef output) { for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) { Var v = i.next(); if (isParameter(v)) { // Assume we no longer know where the parameter comes from // anymore. output.reachingDef.put(v, null); } } // Also, assume we no longer know anything that depends on a parameter. for (Entry<Var, Definition> pair: output.reachingDef.entrySet()) { Definition value = pair.getValue(); if (value == null) { continue; } for (Var dep : value.depends) { if (isParameter(dep)) { output.reachingDef.put(pair.getKey(), null); } } } } private boolean isParameter(Var

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> v) { return v.getParentNode().isParamList(); } /** * Computes all the local variables that rValue reads from and store that * in the def's depends set. */ private void computeDependence(final Definition def, Node rValue) { NodeTraversal.traverse(compiler, rValue, new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { Var dep = jsScope.getVar(n.getString()); if (dep == null) { def.unknownDependencies = true; } else { def.depends.add(dep); } } } }); } /** * Gets the must reaching definition of a given node. * * @param name name of the variable. It can only be names of local variable * that are not function parameters, escaped variables or variables * declared in catch. * @param useNode the location of the use where the definition reaches. */ Definition getDef(String name, Node useNode) { Preconditions.checkArgument(getCfg().hasNode(useNode)); GraphNode<Node, Branch> n = getCfg().getNode(useNode); FlowState<MustDef> state = n.getAnnotation(); return state.getIn().reachingDef.get(jsScope.getVar(name)); } Node getDefNode(String name, Node useNode) { Definition def = getDef(name, useNode); return def == null ? null : def.node; } boolean dependsOnOuterScopeVars(Definition def) { if (def.unknownDependencies) { return true; } for (Var s : def.depends) { if (s.scope != jsScope) { return true; } } return false; } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> return; } finder.visitTree(getAstRoot(compiler)); // TODO(nicksantos|user): This caching behavior is a bit // odd, and only works if you assume the exact call flow that // clients are currently using. In that flow, they call // getProvides(), then remove the goog.provide calls from the // AST, and then call getProvides() again. // // This won't work for any other call flow, or any sort of incremental // compilation scheme. The API needs to be fixed so callers aren't // doing weird things like this, and then we should get rid of the // multiple-scan strategy. provides.addAll(finder.provides); requires.addAll(finder.requires); } else { // Otherwise, look at the source code. if (!generatedDependencyInfoFromSource) { // Note: it's OK to use getName() instead of // getPathRelativeToClosureBase() here because we're not using // this to generate deps files. (We're only using it for // symbol dependencies.) DependencyInfo info = (new JsFileParser(compiler.getErrorManager())) .setIncludeGoogBase(true) .parseFile(getName(), getName(), getCode()); provides.addAll(info.getProvides()); requires.addAll(info.getRequires()); generatedDependencyInfoFromSource = true; } } } private static class DepsFinder { private final List<String> provides = Lists.newArrayList(); private final List<String> requires = Lists.newArrayList(); private final CodingConvention codingConvention = new ClosureCodingConvention(); void visitTree(Node n) { visitSubtree(n, null); } void visitSubtree(Node n, Node parent) { if (n.isCall()) { String require = codingConvention.extractClassNameIfRequire(n, parent); if (require != null) { requires.add(require); } String provide = codingConvention.extractClassNameIfProvide(n, parent); if (provide != null) { provides.add(provide); } return; } else if (parent != null && !parent.isExprResult() && !parent.isScript()) { return; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { visitSubtree(child, n); } } } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end * of the file. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public String getLine(int lineNumber) { return getSourceFile().getLine(lineNumber); } /** * Get a region around the indicated line number. The exact definition of a * region is implementation specific, but it must contain the line indicated * by the line number. A region must not start or end by a carriage return. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public Region getRegion(int lineNumber) { return getSourceFile().getRegion(lineNumber); } public String

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Map; /** * Filters warnings based on in-code {@code @suppress} annotations. * @author nicksantos@google.com (Nick Santos) */ class SuppressDocWarningsGuard extends WarningsGuard { private static final long serialVersionUID = 1L; /** Warnings guards for each suppressible warnings group, indexed by name. */ private final Map<String, DiagnosticGroupWarningsGuard> suppressors = Maps.newHashMap(); /** * The suppressible groups, indexed by name. */ SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) { for (Map.Entry<String, DiagnosticGroup> entry : suppressibleGroups.entrySet()) { suppressors.put( entry.getKey(), new DiagnosticGroupWarningsGuard( entry.getValue(), CheckLevel.OFF)); } } @Override public CheckLevel level(JSError error) { Node node = error.node; if (node != null) { for (Node current = node; current != null; current = current.getParent()) { int type = current.getType(); JSDocInfo info = null; // We only care about function annotations at the FUNCTION and SCRIPT // level. Otherwise, the @suppress annotation has an implicit // dependency on the exact structure of our AST, and that seems like // a bad idea. if (type == Token.FUNCTION) { info = NodeUtil.getFunctionJSDocInfo(current); } else if (type == Token.SCRIPT) { info = current.getJSDocInfo(); } else if (type == Token.ASSIGN) { Node rhs = current.getLastChild(); if (rhs.isFunction()) { info = NodeUtil.getFunctionJSDocInfo(rhs); } } if (info != null) { for (String suppressor : info.getSuppressions()) { WarningsGuard guard = suppressors.get(suppressor); // Some @suppress tags are for other tools, and // may not have a warnings guard. if (guard != null) { CheckLevel newLevel = guard.level(error); if (newLevel != null) { return newLevel; } } } } } } return null; } @Override public int getPriority() { // Happens after path-based filtering, but before other times // of filtering. return WarningsGuard.Priority.SUPPRESS_DOC.value;

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> be scattered across reports). * * Java objects do not support destructors (as in C++) so Tracer is not robust * when exceptions are thrown. Each Tracer object should be wrapped in a * try/finally block so that if an exception is thrown, the Tracer.stop() * method is guaranteed to be called. * * <p>A thread must call {@link Tracer#initCurrentThreadTrace()} to enable the * Tracer logging, otherwise Tracer does nothing. The requirement to call * {@code initCurrentThreadTrace} avoids the situation where Tracer is called * without the explicit knowledge of the application authors because they * happen to use a class in another package that uses Tracer. If {@link * Tracer#logCurrentThreadTrace} is called without calling {@link * Tracer#initCurrentThreadTrace()}, then a Third Eye WARNING message is logged, * which should help track down the problem. * */ final class Tracer { // package-private for access from unit tests static final Logger logger = Logger.getLogger(Tracer.class.getName()); /** * Whether pretty printing is enabled. This is intended to be set once * at application startup. */ private static volatile boolean defaultPrettyPrint; /* This list is guaranteed to only increase in length. It contains * a list of additional statistics that the user wants to keep track * of. */ private static List<TracingStatistic> extraTracingStatistics = new CopyOnWriteArrayList<TracingStatistic>(); /** Values returned by extraTracingStatistics */ private long[] extraTracingValues; /** The type for grouping traces, may be null */ private final @Nullable String type; /** A comment string for the report */ private final String comment; /** Start time of the trace */ private final long startTimeMs; /** Stop time of the trace, non-final */ private long stopTimeMs; /** * Record our starter thread in order to trap Traces that are started in one * thread and stopped in another */ final Thread startThread; /** * We limit the number of events in a Trace in order to catch memory * leaks (a thread that keeps logging events and never clears them). * This number is arbitrary and can be increased if necessary (though * if there are more than 1000 events then the Tracer is probably being * misused). */ static final int MAX_TRACE_SIZE = 1000; /** * For unit testing. Can't use {@link com.google.common.time.Clock} because * this code is in base and has minimal dependencies. */ static interface InternalClock { long currentTimeMillis(); } /** * Default clock that calls through to the system clock. Can be overridden * in unit tests. */ static InternalClock clock = new InternalClock() { @Override public long currentTimeMillis() { return System.currentTimeMillis(); } }; /** * Create and start a tracer. * Both type and comment may be null. See class comment for usage. * * @param type The type for totaling * @param comment Comment about this tracer */ Tracer(@Nullable String type, @Nullable String comment) { this.type = type; this.comment = comment == null ? "" : comment; startTimeMs = clock.currentTimeMillis(); startThread = Thread.currentThread(); if (!extraTracingStatistics.isEmpty()) { int size = extraTracingStatistics.size(); extraTracing

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> private final AbstractCompiler compiler; private final Set<Var> inlinedNewDependencies = Sets.newHashSet(); // These two pieces of data is persistent in the whole execution of enter // scope. private ControlFlowGraph<Node> cfg; private List<Candidate> candidates; private MustBeReachingVariableDef reachingDef; private MaybeReachingVariableUse reachingUses; private static final Predicate<Node> SIDE_EFFECT_PREDICATE = new Predicate<Node>() { @Override public boolean apply(Node n) { // When the node is null it means, we reached the implicit return // where the function returns (possibly without an return statement) if (n == null) { return false; } // TODO(user): We only care about calls to functions that // passes one of the dependent variable to a non-side-effect free // function. if (n.isCall() && NodeUtil.functionCallHasSideEffects(n)) { return true; } if (n.isNew() && NodeUtil.constructorCallHasSideEffects(n)) { return true; } if (n.isDelProp()) { return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && apply(c)) { return true; } } return false; } }; public FlowSensitiveInlineVariables(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void enterScope(NodeTraversal t) { if (t.inGlobalScope()) { return; // Don't even brother. All global variables are likely escaped. } if (LiveVariablesAnalysis.MAX_VARIABLES_TO_ANALYZE < t.getScope().getVarCount()) { return; } // Compute the forward reaching definition. ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true); // Process the body of the function. Preconditions.checkState(t.getScopeRoot().isFunction()); cfa.process(null, t.getScopeRoot().getLastChild()); cfg = cfa.getCfg(); reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler); reachingDef.analyze(); candidates = Lists.newLinkedList(); // Using the forward reaching definition search to find all the inline // candidates new NodeTraversal(compiler, new GatherCandiates()).traverse( t.getScopeRoot().getLastChild()); // Compute the backward reaching use. The CFG can be reused. reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler); reachingUses.analyze(); for (Candidate c : candidates) { if (c.canInline()) { c.inlineVariable(); // If definition c has dependencies, then inlining it may have // introduced new dependencies for our other inlining candidates. // // MustBeReachingVariableDef uses this dependency graph in its // analysis, so some of these candidates may no longer be valid. // We keep track of when the variable dependency graph changed // so that we can back off appropriately. if (!c.defMetadata.depends.isEmpty()) { inlinedNewDependencies.add(t.getScope().getVar(c.varName)); } } } } @Override public void exitScope(NodeTraversal

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> t) {} @Override public void process(Node externs, Node root) { (new NodeTraversal(compiler, this)).traverseRoots(externs, root); } @Override public void visit(NodeTraversal t, Node n, Node parent) { // TODO(user): While the helpers do a subtree traversal on the AST, the // compiler pass itself only traverse the AST to look for function // declarations to perform dataflow analysis on. We could combine // the traversal in DataFlowAnalysis's computeEscaped later to save some // time. } /** * Gathers a list of possible candidates for inlining based only on * information from {@link MustBeReachingVariableDef}. The list will be stored * in {@code candidates} and the validity of each inlining Candidate should * be later verified with {@link Candidate#canInline()} when * {@link MaybeReachingVariableUse} has been performed. */ private class GatherCandiates extends AbstractShallowCallback { @Override public void visit(NodeTraversal t, Node n, Node parent) { DiGraphNode<Node, Branch> graphNode = cfg.getDirectedGraphNode(n); if (graphNode == null) { // Not a CFG node. return; } FlowState<MustDef> state = graphNode.getAnnotation(); final MustDef defs = state.getIn(); final Node cfgNode = n; AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isName()) { // n.getParent() isn't null. This just the case where n is the root // node that gatherCb started at. if (parent == null) { return; } // Make sure that the name node is purely a read. if ((NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n) || parent.isVar() || parent.isInc() || parent.isDec() || parent.isParamList() || parent.isCatch()) { return; } String name = n.getString(); if (compiler.getCodingConvention().isExported(name)) { return; } Definition def = reachingDef.getDef(name, cfgNode); // TODO(nicksantos): We need to add some notion of @const outer // scope vars. We can inline those just fine. if (def != null && !reachingDef.dependsOnOuterScopeVars(def)) { candidates.add(new Candidate(name, def, n, cfgNode)); } } } }; NodeTraversal.traverse(compiler, cfgNode, gatherCb); } } /** * Models the connection between a definition and a use of that definition. */ private class Candidate { // Name of the variable. private final String varName; // Nodes related to the definition. private Node def; private final Definition defMetadata; // Nodes related to the use. private final Node use; private final Node useCfgNode; // Number of uses of the variable within the CFG node that represented the // use in the CFG. private int numUseWithinUseCfgNode; Candidate(String varName, Definition defMetadata, Node use, Node useCfgNode) { Preconditions.checkArgument(use.isName()); this.varName = varName; this

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>: case Token.ARRAYLIT: case Token.OBJECTLIT: case Token.REGEXP: case Token.NEW: return true; } return false; } }, new Predicate<Node>() { @Override public boolean apply(Node input) { // Recurse if the node is not a function. return !input.isFunction(); } })) { return false; } // We can skip the side effect check along the paths of two nodes if // they are just next to each other. if (NodeUtil.isStatementBlock(getDefCfgNode().getParent()) && getDefCfgNode().getNext() != useCfgNode) { // Similar side effect check as above but this time the side effect is // else where along the path. // x = readProp(b); while(modifyProp(b)) {}; print(x); CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch> pathCheck = new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>( cfg, cfg.getDirectedGraphNode(getDefCfgNode()), cfg.getDirectedGraphNode(useCfgNode), SIDE_EFFECT_PREDICATE, Predicates. <DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue(), false); if (pathCheck.somePathsSatisfyPredicate()) { return false; } } return true; } /** * Actual transformation. */ private void inlineVariable() { Node defParent = def.getParent(); Node useParent = use.getParent(); if (def.isAssign()) { Node rhs = def.getLastChild(); rhs.detachFromParent(); // Oh yes! I have grandparent to remove this. Preconditions.checkState(defParent.isExprResult()); while (defParent.getParent().isLabel()) { defParent = defParent.getParent(); } defParent.detachFromParent(); useParent.replaceChild(use, rhs); } else if (defParent.isVar()) { Node rhs = def.getLastChild(); def.removeChild(rhs); useParent.replaceChild(use, rhs); } else { Preconditions.checkState(false, "No other definitions can be inlined."); } compiler.reportCodeChange(); } /** * Set the def node * * @param n A node that has a corresponding CFG node in the CFG. */ private void getDefinition(Node n, Node parent) { AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: if (n.getString().equals(varName) && n.hasChildren()) { def = n; } return; case Token.ASSIGN: Node lhs = n.getFirstChild(); if (lhs.isName() && lhs.getString().equals(varName)) { def = n; } return; } } }; NodeTraversal.traverse(compiler, n, gatherCb); } /** * Computes the number of uses of the variable varName and store it in * numUseWithinUseCfgNode. */ private void getNumUseInUseCfgNode(Node n, Node parant) { AbstractCfgNodeTraversalCallback gatherCb = new AbstractCfgNodeTraversalCallback() { @Override public void visit(NodeTraversal t

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.graph; import java.util.List; /** * A graph that can be dumped to a Graphviz DOT file. * <p> * An object which can be visualized as a graph should implement this interface. * The <code>DotFormatter.toDot</code> function can be used to get a * visualization of the object for debugging purpose. * */ public interface GraphvizGraph { /** * Name of the graph. * * @return Name of the graph. */ String getName(); /** * Graph type. * * @return True if the graph is a directed graph. */ boolean isDirected(); /** * Retrieve a list of nodes in the graph. * * @return A list of nodes in the graph. */ List<GraphvizNode> getGraphvizNodes(); /** * Retrieve a list of edges in the graph. * * @return A list of edges in the graph. */ List<GraphvizEdge> getGraphvizEdges(); /** * A Graphviz node. */ interface GraphvizNode { /** * Retrieves the unique ID. * * @return A the unique ID of the node. */ String getId(); /** * Retrieves color of the node. * * @return The color of the node. */ String getColor(); /** * Retrieves the label of the node. * * @return Label of the node. */ String getLabel(); } /** * A Graphviz edge. */ interface GraphvizEdge { /** * Get the first node in the edge. In a directed node, this will be the * source node. * * @return First node in the edge. */ String getNode1Id(); /** * Get the second node in the edge. In a directed node, this will be the * destination node. * * @return First node in the edge. */ String getNode2Id(); /** * Retrieves color of the edge. * * @return The color of the edge. */ String getColor(); /** * Retrieves the label of the edge. * * @return Label of the edge. */ String getLabel(); } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Bob Jervis * Google Inc. * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package com.google.javascript.rhino.jstype; import static com.google.javascript.rhino.jstype.JSTypeNative.ALL_TYPE; import static com.google.javascript.rhino.jstype.JSTypeNative.CHECKED_UNKNOWN_TYPE; import static com.google.javascript.rhino.jstype.JSTypeNative.NO_TYPE; import static com.google.javascript.rhino.jstype.JSTypeNative.UNKNOWN_TYPE; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.javascript.rhino.jstype.UnionType; import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * A builder for union types. * * @author nicksantos@google.com (Nick Santos) */ class UnionTypeBuilder implements Serializable { private static final long serialVersionUID = 1L; // If the best we can do is say "this object is one of twenty things", // then we should just give up and admit that we have no clue. private static final int DEFAULT_MAX_UNION_SIZE = 20; private final JSTypeRegistry registry; private final List<JSType> alternates = Lists.newArrayList(); private boolean isAllType = false; private boolean isNativeUnknownType = false; private boolean areAllUnknownsChecked = true; private final int maxUnionSize; // Every Union

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>GetterFunction() { return registerFunction != null; } String getName() { return name; } String getExpectedTypeName() { return expectedTypeName; } Node createDefaultValueNode() { switch (this) { case REGISTER_BOOLEAN: return IR.falseNode(); case REGISTER_NUMBER: return IR.number(0); case REGISTER_STRING: return IR.string(""); } throw new IllegalStateException(); } } // A map of function name -> TweakFunction. private static final Map<String, TweakFunction> TWEAK_FUNCTIONS_MAP; static { TWEAK_FUNCTIONS_MAP = Maps.newHashMap(); for (TweakFunction func : TweakFunction.values()) { TWEAK_FUNCTIONS_MAP.put(func.getName(), func); } } ProcessTweaks(AbstractCompiler compiler, boolean stripTweaks, Map<String, Node> compilerDefaultValueOverrides) { this.compiler = compiler; this.stripTweaks = stripTweaks; // Having the map sorted is required for the unit tests to be deterministic. this.compilerDefaultValueOverrides = Maps.newTreeMap(); this.compilerDefaultValueOverrides.putAll(compilerDefaultValueOverrides); } @Override public void process(Node externs, Node root) { CollectTweaksResult result = collectTweaks(root); applyCompilerDefaultValueOverrides(result.tweakInfos); boolean changed = false; if (stripTweaks) { changed = stripAllCalls(result.tweakInfos); } else if (!compilerDefaultValueOverrides.isEmpty()) { changed = replaceGetCompilerOverridesCalls(result.getOverridesCalls); } if (changed) { compiler.reportCodeChange(); } } /** * Passes the compiler default value overrides to the JS by replacing calls * to goog.tweak.getCompilerOverrids_ with a map of tweak ID->default value; */ private boolean replaceGetCompilerOverridesCalls( List<TweakFunctionCall> calls) { for (TweakFunctionCall call : calls) { Node callNode = call.callNode; Node objNode = createCompilerDefaultValueOverridesVarNode(callNode); callNode.getParent().replaceChild(callNode, objNode); } return !calls.isEmpty(); } /** * Removes all CALL nodes in the given TweakInfos, replacing calls to getter * functions with the tweak's default value. */ private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) { for (TweakInfo tweakInfo : tweakInfos.values()) { boolean isRegistered = tweakInfo.isRegistered(); for (TweakFunctionCall functionCall : tweakInfo.functionCalls) { Node callNode = functionCall.callNode; Node parent = callNode.getParent(); if (functionCall.tweakFunc.isGetterFunction()) { Node newValue; if (isRegistered) { newValue = tweakInfo.getDefaultValueNode().cloneNode(); } else { // When we find a getter of an unregistered tweak, there has // already been a warning about it, so now just use a default // value when stripping. TweakFunction registerFunction = functionCall.tweakFunc.registerFunction; newValue = registerFunction.createDefaultValueNode(); } parent.replaceChild(callNode, newValue); } else { Node voidZeroNode = IR.voidNode(IR.number(

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>0).srcref(callNode)) .srcref(callNode); parent.replaceChild(callNode, voidZeroNode); } } } return !tweakInfos.isEmpty(); } /** * Creates a JS object that holds a map of tweakId -> default value override. */ private Node createCompilerDefaultValueOverridesVarNode( Node sourceInformationNode) { Node objNode = IR.objectlit().srcref(sourceInformationNode); for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) { Node objKeyNode = IR.stringKey(entry.getKey()) .copyInformationFrom(sourceInformationNode); Node objValueNode = entry.getValue().cloneNode() .copyInformationFrom(sourceInformationNode); objKeyNode.addChildToBack(objValueNode); objNode.addChildToBack(objKeyNode); } return objNode; } /** Sets the default values of tweaks based on compiler options. */ private void applyCompilerDefaultValueOverrides( Map<String, TweakInfo> tweakInfos) { for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) { String tweakId = entry.getKey(); TweakInfo tweakInfo = tweakInfos.get(tweakId); if (tweakInfo == null) { compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId)); } else { TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc; Node value = entry.getValue(); if (!registerFunc.isValidNodeType(value.getType())) { compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING, tweakId, registerFunc.getName(), registerFunc.getExpectedTypeName())); } else { tweakInfo.defaultValueNode = value; } } } } /** * Finds all calls to goog.tweak functions and emits warnings/errors if any * of the calls have issues. * @return A map of {@link TweakInfo} structures, keyed by tweak ID. */ private CollectTweaksResult collectTweaks(Node root) { CollectTweaks pass = new CollectTweaks(); NodeTraversal.traverse(compiler, root, pass); Map<String, TweakInfo> tweakInfos = pass.allTweaks; for (TweakInfo tweakInfo: tweakInfos.values()) { tweakInfo.emitAllWarnings(); } return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls); } private final static class CollectTweaksResult { final Map<String, TweakInfo> tweakInfos; final List<TweakFunctionCall> getOverridesCalls; CollectTweaksResult(Map<String, TweakInfo> tweakInfos, List<TweakFunctionCall> getOverridesCalls) { this.tweakInfos = tweakInfos; this.getOverridesCalls = getOverridesCalls; } } /** * Processes all calls to goog.tweak functions. */ private final class CollectTweaks extends AbstractPostOrderCallback { final Map<String, TweakInfo> allTweaks = Maps.newHashMap(); final List<TweakFunctionCall> getOverridesCalls = Lists.newArrayList(); @Override public void visit(NodeTraversal t, Node n, Node parent) { if (!n.isCall()) { return; } String callName = n.getFirstChild().getQualifiedName(); TweakFunction

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> this.sourceName = sourceName; this.callNode = callNode; this.tweakFunc = tweakFunc; this.valueNode = valueNode; } Node getIdNode() { return callNode.getFirstChild().getNext(); } } /** * Stores information about a single tweak. */ private final class TweakInfo { final String tweakId; final List<TweakFunctionCall> functionCalls; TweakFunctionCall registerCall; Node defaultValueNode; TweakInfo(String tweakId) { this.tweakId = tweakId; functionCalls = Lists.newArrayList(); } /** * If this tweak is registered, then looks for type warnings in default * value parameters and getter functions. If it is not registered, emits an * error for each function call. */ void emitAllWarnings() { if (isRegistered()) { emitAllTypeWarnings(); } else { emitUnknownTweakErrors(); } } /** * Emits a warning for each default value parameter that has the wrong type * and for each getter function that was used for the wrong type of tweak. */ void emitAllTypeWarnings() { for (TweakFunctionCall call : functionCalls) { Node valueNode = call.valueNode; TweakFunction tweakFunc = call.tweakFunc; TweakFunction registerFunc = registerCall.tweakFunc; if (valueNode != null) { // For register* and overrideDefaultValue calls, ensure the default // value is a literal of the correct type. if (!registerFunc.isValidNodeType(valueNode.getType())) { compiler.report(JSError.make(call.sourceName, valueNode, INVALID_TWEAK_DEFAULT_VALUE_WARNING, tweakId, registerFunc.getName(), registerFunc.getExpectedTypeName())); } } else if (tweakFunc.isGetterFunction()) { // For getter calls, ensure the correct getter was used. if (!tweakFunc.isCorrectRegisterFunction(registerFunc)) { compiler.report(JSError.make(call.sourceName, call.callNode, TWEAK_WRONG_GETTER_TYPE_WARNING, tweakFunc.getName(), registerFunc.getName())); } } } } /** * Emits an error for each function call that was found. */ void emitUnknownTweakErrors() { for (TweakFunctionCall call : functionCalls) { compiler.report(JSError.make(call.sourceName, call.getIdNode(), UNKNOWN_TWEAK_WARNING, tweakId)); } } void addRegisterCall(String sourceName, TweakFunction tweakFunc, Node callNode, Node defaultValueNode) { registerCall = new TweakFunctionCall(sourceName, tweakFunc, callNode, defaultValueNode); functionCalls.add(registerCall); } void addOverrideDefaultValueCall(String sourceName, TweakFunction tweakFunc, Node callNode, Node defaultValueNode) { functionCalls.add(new TweakFunctionCall(sourceName, tweakFunc, callNode, defaultValueNode)); this.defaultValueNode = defaultValueNode; } void addGetterCall(String sourceName, TweakFunction tweakFunc, Node callNode) { functionCalls.add(new TweakFunctionCall(sourceName, tweakFunc, callNode)); } boolean isRegistered() { return registerCall != null; } Node getDefaultValueNode() { Preconditions.checkState(is

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> fnName; private final AbstractCompiler compiler; private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private final Node errorRoot; private final String sourceName; private final Scope scope; private FunctionContents contents = UnknownFunctionContents.get(); private JSType returnType = null; private boolean returnTypeInferred = false; private List<ObjectType> implementedInterfaces = null; private List<ObjectType> extendedInterfaces = null; private ObjectType baseType = null; private ObjectType thisType = null; private boolean isConstructor = false; private boolean isInterface = false; private Node parametersNode = null; private ImmutableList<String> templateTypeNames = ImmutableList.of(); static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning( "JSC_EXTENDS_WITHOUT_TYPEDEF", "@extends used without @constructor or @interface for {0}"); static final DiagnosticType EXTENDS_NON_OBJECT = DiagnosticType.warning( "JSC_EXTENDS_NON_OBJECT", "{0} @extends non-object type {1}"); static final DiagnosticType RESOLVED_TAG_EMPTY = DiagnosticType.warning( "JSC_RESOLVED_TAG_EMPTY", "Could not resolve type in {0} tag of {1}"); static final DiagnosticType IMPLEMENTS_WITHOUT_CONSTRUCTOR = DiagnosticType.warning( "JSC_IMPLEMENTS_WITHOUT_CONSTRUCTOR", "@implements used without @constructor or @interface for {0}"); static final DiagnosticType VAR_ARGS_MUST_BE_LAST = DiagnosticType.warning( "JSC_VAR_ARGS_MUST_BE_LAST", "variable length argument must be last"); static final DiagnosticType OPTIONAL_ARG_AT_END = DiagnosticType.warning( "JSC_OPTIONAL_ARG_AT_END", "optional arguments must be at the end"); static final DiagnosticType INEXISTANT_PARAM = DiagnosticType.warning( "JSC_INEXISTANT_PARAM", "parameter {0} does not appear in {1}''s parameter list"); static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning( "JSC_TYPE_REDEFINITION", "attempted re-definition of type {0}\n" + "found : {1}\n" + "expected: {2}"); static final DiagnosticType TEMPLATE_TYPE_DUPLICATED = DiagnosticType.warning( "JSC_TEMPLATE_TYPE_DUPLICATED", "Only one parameter type must be the template type"); static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.warning( "JSC_TEMPLATE_TYPE_EXPECTED", "The template type must be a parameter type"); static final DiagnosticType THIS_TYPE_NON_OBJECT = DiagnosticType.warning( "JSC_THIS_TYPE_NON_OBJECT", "@this type of a function must be an object\n" + "Actual type: {0}"); private class ExtendedTypeValidator implements Predicate<JSType> { @Override public boolean apply(JSType type) { ObjectType objectType = ObjectType.cast(type); if (objectType == null) { reportWarning(EXTENDS_NON_OBJECT, fnName, type.toString()); return false; } else if (objectType.isEmptyType()) { reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName); return false; } else if (objectType.isUnknownType()) { if (hasMoreTags

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> } /** * Given a qualified name node, returns whether "prototype" is at the end. * For example: * a.b.c => false * a.b.c.prototype => true */ private boolean endsWithPrototype(Node qualifiedName) { return qualifiedName.isGetProp() && qualifiedName.getLastChild().getString().equals("prototype"); } /** * Extracts X from goog.provide('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfProvide(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.provide"); } /** * Extracts X from goog.require('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfRequire(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.require"); } private static String extractClassNameIfGoog(Node node, Node parent, String functionName){ String className = null; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.isGetProp()) { String qualifiedName = callee.getQualifiedName(); if (functionName.equals(qualifiedName)) { Node target = callee.getNext(); if (target != null && target.isString()) { className = target.getString(); } } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } @Override public List<String> identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if ("goog.addDependency".equals(callName.getQualifiedName()) && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.isArrayLit()) { List<String> typeNames = Lists.newArrayList(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.isString()) { typeNames.add(name.getString()); } } return typeNames; } } return super.identifyTypeDeclarationCall(n); } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } @Override public String getSingletonGetterClassName(Node callNode) { Node callArg = callNode.getFirstChild(); String callName = callArg.getQualifiedName(); // Use both the original name and the post-CollapseProperties name. if (!("goog.addSingletonGetter".equals(callName) || "goog$addSingletonGetter".equals(callName)) || callNode.getChildCount() != 2) { return super.getSingletonGetterClassName(callNode); } return callArg.getNext().getQualifiedName(); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType)

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> = n.getFirstChild(); } } if (name != null && name.isQualifiedName()) { String qualifiedName = name.getQualifiedName(); if (!this.convention.isPrivate(qualifiedName)) { Visibility visibility = info.getVisibility(); if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) { ctors.put(qualifiedName, name); } } } } private void visitScriptNode(NodeTraversal t, Node n) { for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) { String ctor = ctorEntry.getKey(); int index = -1; boolean found = false; do { index = ctor.indexOf('.', index +1); String provideKey = index == -1 ? ctor : ctor.substring(0, index); if (provides.containsKey(provideKey)) { found = true; break; } } while (index != -1); if (!found) { compiler.report( t.makeError(ctorEntry.getValue(), checkLevel, MISSING_PROVIDE_WARNING, ctorEntry.getKey())); } } provides.clear(); ctors.clear(); } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.javascript.jscomp.ControlFlowGraph.Branch; import com.google.javascript.jscomp.NodeTraversal.Callback; import com.google.javascript.jscomp.graph.DiGraph.DiGraphNode; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.ArrayDeque; import java.util.Comparator; import java.util.Deque; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; /** * This is a compiler pass that computes a control flow graph. * */ final class ControlFlowAnalysis implements Callback, CompilerPass { /** * Based roughly on the first few pages of * * "Declarative Intraprocedural Flow Analysis of Java Source Code by * Nilsson-Nyman, Hedin, Magnusson & Ekman", * * this pass computes the control flow graph from the AST. However, a full * attribute grammar is not necessary. We will compute the flow edges with a * single post order traversal. The "follow()" of a given node will be * computed recursively in a demand driven fashion. * * As of this moment, we are not performing any inter-procedural analysis * within our framework. */ private final AbstractCompiler compiler; private ControlFlowGraph<Node> cfg; private Map<Node, Integer> astPosition; // TODO(nicksantos): should these be node annotations? private Map<DiGraphNode<Node, Branch>, Integer> nodePriorities; // We order CFG nodes by by looking at the AST positions. // CFG nodes that come first lexically should be visited first, because // they will often be executed first in the source program. private final Comparator<DiGraphNode<Node, Branch>> priorityComparator = new Comparator<DiGraphNode<Node, Branch>>() { @Override public int compare( DiGraphNode<Node, Branch> a, DiGraphNode<Node, Branch> b) { return astPosition.get(a.getValue()) - astPosition.get(b.getValue()); } }; private int astPositionCounter; private int priorityCounter; private final boolean shouldTraverseFunctions; private final boolean edgeAnnotations; // We need to store where we started, in case we aren't doing a flow analysis // for the whole scope. This happens, for example, when running type inference // on only the

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> externs. private Node root; /* * This stack captures the structure of nested TRY blocks. The top of the * stack is the inner most TRY block. A FUNCTION node in this stack implies * that the handler is determined by the caller of the function at runtime. */ private final Deque<Node> exceptionHandler = new ArrayDeque<Node>(); /* * This map is used to handle the follow of FINALLY. For example: * * while(x) { * try { * try { * break; * } catch (a) { * } finally { * foo(); * } * fooFollow(); * } catch (b) { * } finally { * bar(); * } * barFollow(); * } * END(); * * In this case finallyMap will contain a map from: * first FINALLY -> bar() * second FINALLY -> END() * * When we are connecting foo() and bar() to to their respective follow, we * must also look up this map and connect: * foo() -> bar() * bar() -> END */ private final Multimap<Node, Node> finallyMap = HashMultimap.create(); /** * Constructor. * * @param compiler Compiler instance. * @param shouldTraverseFunctions Whether functions should be traversed (true * by default). * @param edgeAnnotations Whether to allow edge annotations. By default, * only node annotations are allowed. */ ControlFlowAnalysis(AbstractCompiler compiler, boolean shouldTraverseFunctions, boolean edgeAnnotations) { this.compiler = compiler; this.shouldTraverseFunctions = shouldTraverseFunctions; this.edgeAnnotations = edgeAnnotations; } ControlFlowGraph<Node> getCfg() { return cfg; } @Override public void process(Node externs, Node root) { this.root = root; astPositionCounter = 0; astPosition = Maps.newHashMap(); nodePriorities = Maps.newHashMap(); cfg = new AstControlFlowGraph(computeFallThrough(root), nodePriorities, edgeAnnotations); NodeTraversal.traverse(compiler, root, this); astPosition.put(null, ++astPositionCounter); // the implicit return is last. // Now, generate the priority of nodes by doing a depth-first // search on the CFG. priorityCounter = 0; DiGraphNode<Node, Branch> entry = cfg.getEntry(); prioritizeFromEntryNode(entry); if (shouldTraverseFunctions) { // If we're traversing inner functions, we need to rank the // priority of them too. for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) { Node value = candidate.getValue(); if (value != null && value.isFunction()) { Preconditions.checkState( !nodePriorities.containsKey(candidate) || candidate == entry); prioritizeFromEntryNode(candidate); } } } // At this point, all reachable nodes have been given a priority, but // unreachable nodes have not been given a priority. Put them last. // Presumably, it doesn't really matter what priority they get, since // this shouldn't happen in real code. for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) { if (!nodePrior

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>ities.containsKey(candidate)) { nodePriorities.put(candidate, ++priorityCounter); } } // Again, the implicit return node is always last. nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter); } /** * Given an entry node, find all the nodes reachable from that node * and prioritize them. */ private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) { PriorityQueue<DiGraphNode<Node, Branch>> worklist = new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator); worklist.add(entry); while (!worklist.isEmpty()) { DiGraphNode<Node, Branch> current = worklist.remove(); if (nodePriorities.containsKey(current)) { continue; } nodePriorities.put(current, ++priorityCounter); List<DiGraphNode<Node, Branch>> successors = cfg.getDirectedSuccNodes(current); for (DiGraphNode<Node, Branch> candidate : successors) { worklist.add(candidate); } } } @Override public boolean shouldTraverse( NodeTraversal nodeTraversal, Node n, Node parent) { astPosition.put(n, astPositionCounter++); switch (n.getType()) { case Token.FUNCTION: if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) { exceptionHandler.push(n); return true; } return false; case Token.TRY: exceptionHandler.push(n); return true; } /* * We are going to stop the traversal depending on what the node's parent * is. * * We are only interested in adding edges between nodes that change control * flow. The most obvious ones are loops and IF-ELSE's. A statement * transfers control to its next sibling. * * In case of an expression tree, there is no control flow within the tree * even when there are short circuited operators and conditionals. When we * are doing data flow analysis, we will simply synthesize lattices up the * expression tree by finding the meet at each expression node. * * For example: within a Token.SWITCH, the expression in question does not * change the control flow and need not to be considered. */ if (parent != null) { switch (parent.getType()) { case Token.FOR: // Only traverse the body of the for loop. return n == parent.getLastChild(); // Skip the conditions. case Token.IF: case Token.WHILE: case Token.WITH: return n != parent.getFirstChild(); case Token.DO: return n != parent.getFirstChild().getNext(); // Only traverse the body of the cases case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.LABEL: return n != parent.getFirstChild(); case Token.FUNCTION: return n == parent.getFirstChild().getNext().getNext(); case Token.CONTINUE: case Token.BREAK: case Token.EXPR_RESULT: case Token.VAR: case Token.RETURN: case Token.THROW: return false; case Token.TRY: /* Just before we are about to visit the second child of the TRY node, * we know that we will be visiting either the CATCH or the FINALLY. * In other words, we know that the post order traversal

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> of the TRY * block has been finished, no more exceptions can be caught by the * handler at this TRY block and should be taken out of the stack. */ if (n == parent.getFirstChild().getNext()) { Preconditions.checkState(exceptionHandler.peek() == parent); exceptionHandler.pop(); } } } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.IF: handleIf(n); return; case Token.WHILE: handleWhile(n); return; case Token.DO: handleDo(n); return; case Token.FOR: handleFor(n); return; case Token.SWITCH: handleSwitch(n); return; case Token.CASE: handleCase(n); return; case Token.DEFAULT_CASE: handleDefault(n); return; case Token.BLOCK: case Token.SCRIPT: handleStmtList(n); return; case Token.FUNCTION: handleFunction(n); return; case Token.EXPR_RESULT: handleExpr(n); return; case Token.THROW: handleThrow(n); return; case Token.TRY: handleTry(n); return; case Token.CATCH: handleCatch(n); return; case Token.BREAK: handleBreak(n); return; case Token.CONTINUE: handleContinue(n); return; case Token.RETURN: handleReturn(n); return; case Token.WITH: handleWith(n); return; case Token.LABEL: return; default: handleStmt(n); return; } } private void handleIf(Node node) { Node thenBlock = node.getFirstChild().getNext(); Node elseBlock = thenBlock.getNext(); createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock)); if (elseBlock == null) { createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); // not taken branch } else { createEdge(node, Branch.ON_FALSE, computeFallThrough(elseBlock)); } connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleWhile(Node node) { // Control goes to the first statement if the condition evaluates to true. createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild().getNext())); // Control goes to the follow() if the condition evaluates to false. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleDo(Node node) { // The first edge can be the initial iteration as well as the iterations // after. createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild())); // The edge that leaves the do loop if the condition fails. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); connectToPossibleExceptionHandler( node, NodeUtil.getConditionExpression(node)); } private void handleFor(Node forNode) { if (forNode.getChildCount() == 4) { // We have for (init;

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> cond; iter) { body } Node init = forNode.getFirstChild(); Node cond = init.getNext(); Node iter = cond.getNext(); Node body = iter.getNext(); // After initialization, we transfer to the FOR which is in charge of // checking the condition (for the first time). createEdge(init, Branch.UNCOND, forNode); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode, this)); // The end of the body will have a unconditional branch to our iter // (handled by calling computeFollowNode of the last instruction of the // body. Our iter will jump to the forNode again to another condition // check. createEdge(iter, Branch.UNCOND, forNode); connectToPossibleExceptionHandler(init, init); connectToPossibleExceptionHandler(forNode, cond); connectToPossibleExceptionHandler(iter, iter); } else { // We have for (item in collection) { body } Node item = forNode.getFirstChild(); Node collection = item.getNext(); Node body = collection.getNext(); // The collection behaves like init. createEdge(collection, Branch.UNCOND, forNode); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode, this)); connectToPossibleExceptionHandler(forNode, collection); } } private void handleSwitch(Node node) { // Transfer to the first non-DEFAULT CASE. if there are none, transfer // to the DEFAULT or the EMPTY node. Node next = getNextSiblingOfType( node.getFirstChild().getNext(), Token.CASE, Token.EMPTY); if (next != null) { // Has at least one CASE or EMPTY createEdge(node, Branch.UNCOND, next); } else { // Has no CASE but possibly a DEFAULT if (node.getFirstChild().getNext() != null) { createEdge(node, Branch.UNCOND, node.getFirstChild().getNext()); } else { // No CASE, no DEFAULT createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleCase(Node node) { // Case is a bit tricky....First it goes into the body if condition is true. createEdge(node, Branch.ON_TRUE, node.getFirstChild().getNext()); // Look for the next CASE, skipping over DEFAULT. Node next = getNextSiblingOfType(node.getNext(), Token.CASE); if (next != null) { // Found a CASE Preconditions.checkState(next.isCase()); createEdge(node, Branch.ON_FALSE, next); } else { // No more CASE found, go back and search for a DEFAULT. Node parent = node.getParent(); Node deflt = getNextSiblingOfType( parent.getFirstChild().getNext(), Token.DEFAULT_CASE); if (deflt != null) { // Has a DEFAULT createEdge(node, Branch.ON_FALSE, deflt); } else { // No DEFAULT found, go to

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> the follow of the SWITCH. createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this)); } } connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleDefault(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleWith(Node node) { // Directly goes to the body. It should not transfer to the next case. createEdge(node, Branch.UNCOND, node.getLastChild()); connectToPossibleExceptionHandler(node, node.getFirstChild()); } private void handleStmtList(Node node) { Node parent = node.getParent(); // Special case, don't add a block of empty CATCH block to the graph. if (node.isBlock() && parent != null && parent.isTry() && NodeUtil.getCatchBlock(parent) == node && !NodeUtil.hasCatchHandler(node)) { return; } // A block transfer control to its first child if it is not empty. Node child = node.getFirstChild(); // Function declarations are skipped since control doesn't go into that // function (unless it is called) while (child != null && child.isFunction()) { child = child.getNext(); } if (child != null) { createEdge(node, Branch.UNCOND, computeFallThrough(child)); } else { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); } // Synthetic blocks if (parent != null) { switch (parent.getType()) { case Token.DEFAULT_CASE: case Token.CASE: case Token.TRY: break; default: if (node.isBlock() && node.isSyntheticBlock()) { createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this)); } break; } } } private void handleFunction(Node node) { // A block transfer control to its first child if it is not empty. Preconditions.checkState(node.getChildCount() >= 3); createEdge(node, Branch.UNCOND, computeFallThrough(node.getFirstChild().getNext().getNext())); Preconditions.checkState(exceptionHandler.peek() == node); exceptionHandler.pop(); } private void handleExpr(Node node) { createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } private void handleThrow(Node node) { connectToPossibleExceptionHandler(node, node); } private void handleTry(Node node) { createEdge(node, Branch.UNCOND, node.getFirstChild()); } private void handleCatch(Node node) { createEdge(node, Branch.UNCOND, node.getLastChild()); } private void handleBreak(Node node) { String label = null; // See if it is a break with label. if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node previous = null; Node lastJump; Node parent = node.getParent(); /* * Continuously look up the ancestor tree for the BREAK target or the target * with the corresponding label and connect to it. If along the path we * discover a FINALLY, we will connect the

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> BREAK to that FINALLY. From then * on, we will just record the control flow changes in the finallyMap. This * is due to the fact that we need to connect any node that leaves its own * FINALLY block to the outer FINALLY or the BREAK's target but those nodes * are not known yet due to the way we traverse the nodes. */ for (cur = node, lastJump = node; !isBreakTarget(cur, label); cur = parent, parent = parent.getParent()) { if (cur.isTry() && NodeUtil.hasFinally(cur) && cur.getLastChild() != previous) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFallThrough( cur.getLastChild())); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } if (parent == null) { if (compiler.isIdeMode()) { // In IDE mode, we expect that the data flow graph may // not be well-formed. return; } else { throw new IllegalStateException("Cannot find break target."); } } previous = cur; } if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this)); } else { finallyMap.put(lastJump, computeFollowNode(cur, this)); } } private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node previous = null; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.isTry() && NodeUtil.hasFinally(cur) && cur.getLastChild() != previous) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); previous = cur; } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext(); } if (lastJump == node) { createEdge(node, Branch.UNCOND, iter); } else { finallyMap.put(lastJump, iter); } } private void handleReturn(Node node) { Node lastJump = null; for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) { Node curHandler = iter.next(); if (curHandler.isFunction()) { break; } if (NodeUtil.hasFinally(curHandler)) { if (lastJump == null) { createEdge(node, Branch.UNCOND, curHandler.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(curHandler.getLastChild())); } lastJump = curHandler; } } if (node.hasChildren()) { connectToPossibleExceptionHandler(node, node

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>.getFirstChild()); } if (lastJump == null) { createEdge(node, Branch.UNCOND, null); } else { finallyMap.put(lastJump, null); } } private void handleStmt(Node node) { // Simply transfer to the next line. createEdge(node, Branch.UNCOND, computeFollowNode(node, this)); connectToPossibleExceptionHandler(node, node); } static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) { return computeFollowNode(node, node, cfa); } static Node computeFollowNode(Node node) { return computeFollowNode(node, node, null); } /** * Computes the follow() node of a given node and its parent. There is a side * effect when calling this function. If this function computed an edge that * exists a FINALLY, it'll attempt to connect the fromNode to the outer * FINALLY according to the finallyMap. * * @param fromNode The original source node since {@code node} is changed * during recursion. * @param node The node that follow() should compute. */ private static Node computeFollowNode( Node fromNode, Node node, ControlFlowAnalysis cfa) { /* * This is the case where: * * 1. Parent is null implies that we are transferring control to the end of * the script. * * 2. Parent is a function implies that we are transferring control back to * the caller of the function. * * 3. If the node is a return statement, we should also transfer control * back to the caller of the function. * * 4. If the node is root then we have reached the end of what we have been * asked to traverse. * * In all cases we should transfer control to a "symbolic return" node. * This will make life easier for DFAs. */ Node parent = node.getParent(); if (parent == null || parent.isFunction() || (cfa != null && node == cfa.root)) { return null; } // If we are just before a IF/WHILE/DO/FOR: switch (parent.getType()) { // The follow() of any of the path from IF would be what follows IF. case Token.IF: return computeFollowNode(fromNode, parent, cfa); case Token.CASE: case Token.DEFAULT_CASE: // After the body of a CASE, the control goes to the body of the next // case, without having to go to the case condition. if (parent.getNext() != null) { if (parent.getNext().isCase()) { return parent.getNext().getFirstChild().getNext(); } else if (parent.getNext().isDefaultCase()) { return parent.getNext().getFirstChild(); } else { Preconditions.checkState(false, "Not reachable"); } } else { return computeFollowNode(fromNode, parent, cfa); } break; case Token.FOR: if (NodeUtil.isForIn(parent)) { return parent; } else { return parent.getFirstChild().getNext().getNext(); } case Token.WHILE: case Token.DO: return parent; case Token.TRY: // If we are coming out of the TRY block... if (parent.getFirstChild() == node) { if (

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(parent.getLastChild()); } else { // and have no FINALLY. return computeFollowNode(fromNode, parent, cfa); } // CATCH block. } else if (NodeUtil.getCatchBlock(parent) == node){ if (NodeUtil.hasFinally(parent)) { // and have FINALLY block. return computeFallThrough(node.getNext()); } else { return computeFollowNode(fromNode, parent, cfa); } // If we are coming out of the FINALLY block... } else if (parent.getLastChild() == node){ if (cfa != null) { for (Node finallyNode : cfa.finallyMap.get(parent)) { cfa.createEdge(fromNode, Branch.ON_EX, finallyNode); } } return computeFollowNode(fromNode, parent, cfa); } } // Now that we are done with the special cases follow should be its // immediate sibling, unless its sibling is a function Node nextSibling = node.getNext(); // Skip function declarations because control doesn't get pass into it. while (nextSibling != null && nextSibling.isFunction()) { nextSibling = nextSibling.getNext(); } if (nextSibling != null) { return computeFallThrough(nextSibling); } else { // If there are no more siblings, control is transferred up the AST. return computeFollowNode(fromNode, parent, cfa); } } /** * Computes the destination node of n when we want to fallthrough into the * subtree of n. We don't always create a CFG edge into n itself because of * DOs and FORs. */ static Node computeFallThrough(Node n) { switch (n.getType()) { case Token.DO: return computeFallThrough(n.getFirstChild()); case Token.FOR: if (NodeUtil.isForIn(n)) { return n.getFirstChild().getNext(); } return computeFallThrough(n.getFirstChild()); case Token.LABEL: return computeFallThrough(n.getLastChild()); default: return n; } } /** * Connects the two nodes in the control flow graph. * * @param fromNode Source. * @param toNode Destination. */ private void createEdge(Node fromNode, ControlFlowGraph.Branch branch, Node toNode) { cfg.createNode(fromNode); cfg.createNode(toNode); cfg.connectIfNotFound(fromNode, branch, toNode); } /** * Connects cfgNode to the proper CATCH block if target subtree might throw * an exception. If there are FINALLY blocks reached before a CATCH, it will * make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (handler.isFunction()) { return; } Preconditions.checkState(handler.isTry()); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } } /** * Get the next sibling (including itself) of one of the given types. */ private static Node getNextSiblingOfType(Node first, int ... types) { for (Node c = first; c != null; c = c.getNext()) { for (int type : types) { if (c.getType() == type) { return c; } } } return null; } /** * Checks if target is actually the break target of labeled continue. The * label can be null if it is an unlabeled break. */ public static boolean isBreakTarget(Node target, String label) { return isBreakStructure(target, label != null) && matchLabel(target.getParent(), label); } /** * Checks if target is actually the continue target of labeled continue. The * label can be null if it is an unlabeled continue. */ private static boolean isContinueTarget( Node target, Node parent, String label) { return isContinueStructure(target) && matchLabel(parent, label); } /** * Check if label is actually referencing the target control structure. If * label is null, it always returns true. */ private static boolean matchLabel(Node target, String label) { if (label == null) { return true; } while (target.isLabel()) { if (target.getFirstChild().getString().equals(label)) { return true; } target = target.getParent(); } return false; } /** * Determines if the subtree might throw an exception. */ public static boolean mayThrowException(Node n) { switch (n.getType()) { case Token.CALL: case Token.GETPROP: case Token.GETELEM: case Token.THROW: case Token.NEW: case Token.ASSIGN: case Token.INC: case Token.DEC: case Token.INSTANCEOF: return true; case Token.FUNCTION: return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) { return true; } } return false; } /** * Determines whether the given node can be terminated with a BREAK node. */ static boolean isBreakStructure(Node n, boolean labeled) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.SWITCH: return true; case Token.BLOCK: case Token.IF: case Token.TRY: return labeled; default: return false; } } /** * Determines whether the given node can be advanced with a CONTINUE node. */ static boolean isContinueStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> /** * Get the TRY block with a CATCH that would be run if n throws an exception. * @return The CATCH node or null if it there isn't a CATCH before the * the function terminates. */ static Node getExceptionHandler(Node n) { for (Node cur = n; !cur.isScript() && !cur.isFunction(); cur = cur.getParent()) { Node catchNode = getCatchHandlerForBlock(cur); if (catchNode != null) { return catchNode; } } return null; } /** * Locate the catch BLOCK given the first block in a TRY. * @return The CATCH node or null there is no catch handler. */ static Node getCatchHandlerForBlock(Node block) { if (block.isBlock() && block.getParent().isTry() && block.getParent().getFirstChild() == block) { for (Node s = block.getNext(); s != null; s = s.getNext()) { if (NodeUtil.hasCatchHandler(s)) { return s.getFirstChild(); } } } return null; } /** * A {@link ControlFlowGraph} which provides a node comparator based on the * pre-order traversal of the AST. */ private static class AstControlFlowGraph extends ControlFlowGraph<Node> { private final Map<DiGraphNode<Node, Branch>, Integer> priorities; /** * Constructor. * @param entry The entry node. * @param priorities The map from nodes to position in the AST (to be * filled by the {@link ControlFlowAnalysis#shouldTraverse}). */ private AstControlFlowGraph(Node entry, Map<DiGraphNode<Node, Branch>, Integer> priorities, boolean edgeAnnotations) { super(entry, true /* node annotations */, edgeAnnotations); this.priorities = priorities; } @Override /** * Returns a node comparator based on the pre-order traversal of the AST. * @param isForward x 'before' y in the pre-order traversal implies * x 'less than' y (if true) and x 'greater than' y (if false). */ public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator( boolean isForward) { if (isForward) { return new Comparator<DiGraphNode<Node, Branch>>() { @Override public int compare( DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) { return getPosition(n1) - getPosition(n2); } }; } else { return new Comparator<DiGraphNode<Node, Branch>>() { @Override public int compare( DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) { return getPosition(n2) - getPosition(n1); } }; } } /** * Gets the pre-order traversal position of the given node. * @return An arbitrary counter used for comparing positions. */ private int getPosition(DiGraphNode<Node, Branch> n) { Integer priority = priorities.get(n); Preconditions.checkNotNull(priority); return priority; } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>keeping. RecordType maybeRecordType = greatestSubtype.toMaybeRecordType(); if (maybeRecordType != null && maybeRecordType.isSynthetic()) { continue; } return true; } } } return false; } /** * Returns each type that has a property {@code propertyName} defined on it. * * Like most types in our type system, the collection of types returned * will be collapsed. This means that if a type is defined on * {@code Object} and on {@code Array}, it would be reasonable for this * method to return either {@code [Object, Array]} or just {@code [Object]}. */ public Iterable<JSType> getTypesWithProperty(String propertyName) { if (typesIndexedByProperty.containsKey(propertyName)) { return typesIndexedByProperty.get(propertyName).getAlternates(); } else { return ImmutableList.of(); } } /** * Returns each reference type that has a property {@code propertyName} * defined on it. * * Unlike most types in our type system, the collection of types returned * will not be collapsed. This means that if a type is defined on * {@code Object} and on {@code Array}, this method must return * {@code [Object, Array]}. It would not be correct to collapse them to * {@code [Object]}. */ public Iterable<ObjectType> getEachReferenceTypeWithProperty( String propertyName) { if (eachRefTypeIndexedByProperty.containsKey(propertyName)) { return eachRefTypeIndexedByProperty.get(propertyName).values(); } else { return ImmutableList.of(); } } /** * Finds the common supertype of the two given object types. */ ObjectType findCommonSuperObject(ObjectType a, ObjectType b) { List<ObjectType> stackA = getSuperStack(a); List<ObjectType> stackB = getSuperStack(b); ObjectType result = getNativeObjectType(JSTypeNative.OBJECT_TYPE); while (!stackA.isEmpty() && !stackB.isEmpty()) { ObjectType currentA = stackA.remove(stackA.size() - 1); ObjectType currentB = stackB.remove(stackB.size() - 1); if (currentA.isEquivalentTo(currentB)) { result = currentA; } else { return result; } } return result; } private static List<ObjectType> getSuperStack(ObjectType a) { List<ObjectType> stack = Lists.newArrayListWithExpectedSize(5); for (ObjectType current = a; current != null; current = current.getImplicitPrototype()) { stack.add(current); } return stack; } /** * Increments the current generation. Clients must call this in order to * move to the next generation of type resolution, allowing types to attempt * resolution again. */ public void incrementGeneration() { for (NamedType type : resolvedNamedTypes.values()) { type.clearResolved(); } unresolvedNamedTypes.putAll(resolvedNamedTypes); resolvedNamedTypes.clear(); } boolean isLastGeneration() { return lastGeneration; } /** * Sets whether this is the last generation. In the last generation, * {@link NamedType} warns about unresolved types. */ public void setLastGeneration(boolean lastGeneration) { this.lastGeneration = lastGeneration; } /** * Tells the type

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>OptionalNullableType(JSType type) { return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE), getNativeType(JSTypeNative.NULL_TYPE)); } /** * Creates a union type whose variants are the arguments. */ public JSType createUnionType(JSType... variants) { UnionTypeBuilder builder = new UnionTypeBuilder(this); for (JSType type : variants) { builder.addAlternate(type); } return builder.build(); } /** * Creates a union type whose variants are the built-in types specified * by the arguments. */ public JSType createUnionType(JSTypeNative... variants) { UnionTypeBuilder builder = new UnionTypeBuilder(this); for (JSTypeNative typeId : variants) { builder.addAlternate(getNativeType(typeId)); } return builder.build(); } /** * Creates an enum type. */ public EnumType createEnumType( String name, Node source, JSType elementsType) { return new EnumType(this, name, source, elementsType); } /** * Creates an arrow type, an abstract representation of the parameters * and return value of a function. * * @param parametersNode the parameters' types, formatted as a Node with * param names and optionality info. * @param returnType the function's return type */ ArrowType createArrowType(Node parametersNode, JSType returnType) { return new ArrowType(this, parametersNode, returnType); } /** * Creates an arrow type with an unknown return type. * * @param parametersNode the parameters' types, formatted as a Node with * param names and optionality info. */ ArrowType createArrowType(Node parametersNode) { return new ArrowType(this, parametersNode, null); } /** * Creates a function type. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createFunctionType( JSType returnType, JSType... parameterTypes) { return createFunctionType(returnType, createParameters(parameterTypes)); } /** * Creates a function type. The last parameter type of the function is * considered a variable length argument. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createFunctionTypeWithVarArgs( JSType returnType, List<JSType> parameterTypes) { return createFunctionType( returnType, createParametersWithVarArgs(parameterTypes)); } /** * Creates a function type. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createFunctionType( JSType returnType, List<JSType> parameterTypes) { return createFunctionType(returnType, createParameters(parameterTypes)); } /** * Creates a function type. The last parameter type of the function is * considered a variable length argument. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createFunctionTypeWithVarArgs( JSType returnType, JSType... parameterTypes) { return createFunctionType( returnType, createParametersWithVarArgs(parameterTypes)); } /** * Creates a function type. The last parameter type of the function is * considered a variable length argument. * * @param returnType the function's return type *

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> @param parameterTypes the parameters' types */ private FunctionType createNativeFunctionTypeWithVarArgs( JSType returnType, JSType... parameterTypes) { return createNativeFunctionType( returnType, createParametersWithVarArgs(parameterTypes)); } /** * Creates a function type which can act as a constructor. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createConstructorType( JSType returnType, JSType... parameterTypes) { return createConstructorType( null, null, createParameters(parameterTypes), returnType); } /** * Creates a function type which can act as a constructor. The last * parameter type of the constructor is considered a variable length argument. * * @param returnType the function's return type * @param parameterTypes the parameters' types */ public FunctionType createConstructorTypeWithVarArgs( JSType returnType, JSType... parameterTypes) { return createConstructorType( null, null, createParametersWithVarArgs(parameterTypes), returnType); } /** * Creates a function type in which {@code this} refers to an object instance. * * @param instanceType the type of {@code this} * @param returnType the function's return type * @param parameterTypes the parameters' types */ public JSType createFunctionType(ObjectType instanceType, JSType returnType, List<JSType> parameterTypes) { return new FunctionBuilder(this) .withParamsNode(createParameters(parameterTypes)) .withReturnType(returnType) .withTypeOfThis(instanceType) .build(); } /** * Creates a function type in which {@code this} refers to an object instance. * The last parameter type of the function is considered a variable length * argument. * * @param instanceType the type of {@code this} * @param returnType the function's return type * @param parameterTypes the parameters' types */ public JSType createFunctionTypeWithVarArgs(ObjectType instanceType, JSType returnType, List<JSType> parameterTypes) { return new FunctionBuilder(this) .withParamsNode(createParametersWithVarArgs(parameterTypes)) .withReturnType(returnType) .withTypeOfThis(instanceType) .build(); } /** * Creates a tree hierarchy representing a typed argument list. * * @param parameterTypes the parameter types. * @return a tree hierarchy representing a typed argument list. */ public Node createParameters(List<JSType> parameterTypes) { return createParameters( parameterTypes.toArray(new JSType[parameterTypes.size()])); } /** * Creates a tree hierarchy representing a typed argument list. The last * parameter type is considered a variable length argument. * * @param parameterTypes the parameter types. The last element of this array * is considered a variable length argument. * @return a tree hierarchy representing a typed argument list. */ public Node createParametersWithVarArgs(List<JSType> parameterTypes) { return createParametersWithVarArgs( parameterTypes.toArray(new JSType[parameterTypes.size()])); } /** * Creates a tree hierarchy representing a typed argument list. * * @param parameterTypes the parameter types. * @return a tree hierarchy representing a typed argument list. */ public Node createParameters(JSType... parameterTypes) { return createParameters(false, parameterTypes); } /** * Creates a tree hierarchy representing

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> boolean addSuccess = paramBuilder.addOptionalParams(type); if (!addSuccess) { reporter.warning( ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"), sourceName, arg.getLineno(), arg.getCharno()); } } else { paramBuilder.addRequiredParams(type); } } } current = current.getNext(); } JSType returnType = createFromTypeNodesInternal(current, sourceName, scope); return new FunctionBuilder(this) .withParams(paramBuilder) .withReturnType(returnType) .withTypeOfThis(thisType) .setIsConstructor(isConstructor) .build(); } throw new IllegalStateException( "Unexpected node in type expression: " + n.toString()); } /** * Creates a RecordType from the nodes representing said record type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ private JSType createRecordTypeFromNodes(Node n, String sourceName, StaticScope<JSType> scope) { RecordTypeBuilder builder = new RecordTypeBuilder(this); // For each of the fields in the record type. for (Node fieldTypeNode = n.getFirstChild(); fieldTypeNode != null; fieldTypeNode = fieldTypeNode.getNext()) { // Get the property's name. Node fieldNameNode = fieldTypeNode; boolean hasType = false; if (fieldTypeNode.getType() == Token.COLON) { fieldNameNode = fieldTypeNode.getFirstChild(); hasType = true; } String fieldName = fieldNameNode.getString(); // TODO(user): Move this into the lexer/parser. // Remove the string literal characters around a field name, // if any. if (fieldName.startsWith("'") || fieldName.startsWith("\"")) { fieldName = fieldName.substring(1, fieldName.length() - 1); } // Get the property's type. JSType fieldType = null; if (hasType) { // We have a declared type. fieldType = createFromTypeNodesInternal( fieldTypeNode.getLastChild(), sourceName, scope); } else { // Otherwise, the type is UNKNOWN. fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } // Add the property to the record. if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) { // Duplicate field name, warning and skip reporter.warning( "Duplicate record field " + fieldName, sourceName, n.getLineno(), fieldNameNode.getCharno()); } } return builder.build(); } /** * Sets the template type name. */ public void setTemplateTypeNames(List<String> names) { Preconditions.checkNotNull(names); for (String name : names) { templateTypes.put(name, new TemplateType(this, name)); } } /** * Clears the template type name. */ public void clearTemplateTypeNames() { templateTypes.clear(); } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>} if a template type name was already defined. */ public boolean recordTemplateTypeNames(List<String> names) { if (currentInfo.declareTemplateTypeNames(names)) { populated = true; return true; } else { return false; } } /** * Records a thrown type. */ public boolean recordThrowType(JSTypeExpression type) { if (!hasAnySingletonTypeTags()) { currentInfo.declareThrows(type); populated = true; return true; } return false; } /** * Records a throw type's description. * * @return {@code true} if the type's description was recorded and * {@code false} if a description with the same type was already defined */ public boolean recordThrowDescription( JSTypeExpression type, String description) { if (currentInfo.documentThrows(type, description)) { populated = true; return true; } else { return false; } } /** * Adds an author to the current information. */ public boolean addAuthor(String author) { if (currentInfo.documentAuthor(author)) { populated = true; return true; } else { return false; } } /** * Adds a reference ("@see") to the current information. */ public boolean addReference(String reference) { if (currentInfo.documentReference(reference)) { populated = true; return true; } else { return false; } } /** * Records that the {@link JSDocInfo} being built should have its * {@link JSDocInfo#isConsistentIdGenerator()} flag set to * {@code true}. * * @return {@code true} if the consistentIdGenerator flag was recorded and * {@code false} if it was already recorded */ public boolean recordConsistentIdGenerator() { if (!currentInfo.isConsistentIdGenerator()) { currentInfo.setConsistentIdGenerator(true); populated = true; return true; } else { return false; } } /** * Records the version. */ public boolean recordVersion(String version) { if (currentInfo.documentVersion(version)) { populated = true; return true; } else { return false; } } /** * Records the deprecation reason. */ public boolean recordDeprecationReason(String reason) { if (currentInfo.setDeprecationReason(reason)) { populated = true; return true; } else { return false; } } /** * Records the list of suppressed warnings. */ public boolean recordSuppressions(Set<String> suppressions) { if (currentInfo.setSuppressions(suppressions)) { populated = true; return true; } else { return false; } } /** * Records the list of modifies warnings. */ public boolean recordModifies(Set<String> modifies) { if (!hasAnySingletonSideEffectTags() && currentInfo.setModifies(modifies)) { populated = true; return true; } else { return false; } } /** * Records a type. * * @return {@code true} if the type was recorded and {@code false} if * it is invalid or was already defined */ public boolean recordType(JSTypeExpression type) { if

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.Callback; import com.google.javascript.jscomp.graph.LinkedDirectedGraph; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import java.util.Comparator; /** * Control flow graph. * * * @param <N> The instruction type of the control flow graph. */ class ControlFlowGraph<N> extends LinkedDirectedGraph<N, ControlFlowGraph.Branch> { /** * A special node marked by the node value key null to a singleton * "return" when control is transferred outside of the current control flow * graph. */ private final DiGraphNode<N, ControlFlowGraph.Branch> implicitReturn; private final DiGraphNode<N, ControlFlowGraph.Branch> entry; /** * Constructor. */ ControlFlowGraph( N entry, boolean nodeAnnotations, boolean edgeAnnotations) { super(nodeAnnotations, edgeAnnotations); implicitReturn = createDirectedGraphNode(null); this.entry = createDirectedGraphNode(entry); } /** * Gets the implicit return node. * * @return Return node. */ public DiGraphNode<N, ControlFlowGraph.Branch> getImplicitReturn() { return implicitReturn; } /** * Gets the entry point of the control flow graph. In general, this should be * the beginning of the global script or beginning of a function. * * @return The entry point. */ public DiGraphNode<N, ControlFlowGraph.Branch> getEntry() { return entry; } /** * Checks whether node is the implicit return. * * @param node Node. * @return True if the node is the implicit return. */ public boolean isImplicitReturn( DiGraphNode<N, ControlFlowGraph.Branch> node) { return node == implicitReturn; } /** * Connects the node to the explicit return. * * @param srcValue Node. * @param edgeValue Edge. */ public void connectToImplicitReturn(N srcValue, Branch edgeValue) { super.connect(srcValue, edgeValue, null); } /** * Gets a comparator for the nodes. The default implementation returns * {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}. * @param isForward Whether the comparator sorts the nodes in the direction of * the flow. * @return a comparator or null (in particular, if not overridden) */ public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator( boolean isForward) { return null; } /** * The edge object

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> for the control flow graph. */ public static enum Branch { /** Edge is taken if the condition is true. */ ON_TRUE, /** Edge is taken if the condition is false. */ ON_FALSE, /** Unconditional branch. */ UNCOND, /** * Exception-handling code paths. * Conflates two kind of control flow passing: * - An exception is thrown, and falls into a catch or finally block * - During exception handling, a finally block finishes and control * passes to the next finally block. * In theory, we need 2 different edge types. In practice, we * can just treat them as "the edges we can't really optimize". */ ON_EX, /** Possible folded-away template */ SYN_BLOCK; public boolean isConditional() { return this == ON_TRUE || this == ON_FALSE; } } /** * Abstract callback to visit a control flow graph node without going into * subtrees of the node that is also represented by another control flow graph * node. * * <p>For example, traversing an IF node as root will visit the two subtree * pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and * {@link ControlFlowGraph.Branch#ON_FALSE} edge. */ public abstract static class AbstractCfgNodeTraversalCallback implements Callback { @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (parent == null) { return true; } return !isEnteringNewCfgNode(n); } } /** * @return True if n should be represented by a new CFG node in the control * flow graph. */ public static boolean isEnteringNewCfgNode(Node n) { Node parent = n.getParent(); switch (parent.getType()) { case Token.BLOCK: case Token.SCRIPT: case Token.TRY: return true; case Token.FUNCTION: // A function node represents the start of a function where the name // is bleed into the local scope and parameters has been assigned // to the formal argument names. The node includes the name of the // function and the LP list since we assume the whole set up process // is atomic without change in control flow. The next change of // control is going into the function's body represent by the second // child. return n != parent.getFirstChild().getNext(); case Token.WHILE: case Token.DO: case Token.IF: // Theses control structure is represented by its node that holds the // condition. Each of them is a branch node based on its condition. return NodeUtil.getConditionExpression(parent) != n; case Token.FOR: // The FOR(;;) node differs from other control structure in that // it has a initialization and a increment statement. Those // two statements have its corresponding CFG nodes to represent them. // The FOR node represents the condition check for each iteration. // That way the following: // for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to // var x = 0; while(x<10) { x++; } if (NodeUtil.isForIn(parent)) { // TODO(user): Investigate how we should handle the case where // we have a very complex expression inside the FOR-IN header. return n != parent.getFirst

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> If visibility is not specified, we just assume that visibility // is inherited from the super class. INHERITED } private static final class LazilyInitializedInfo implements Serializable { private static final long serialVersionUID = 1L; // Function information JSTypeExpression baseType = null; List<JSTypeExpression> extendedInterfaces = null; List<JSTypeExpression> implementedInterfaces = null; Map<String, JSTypeExpression> parameters = null; List<JSTypeExpression> thrownTypes = null; ImmutableList<String> templateTypeNames = null; // Other information String description = null; String meaning = null; String deprecated = null; String license = null; Set<String> suppressions = null; Set<String> modifies = null; String lendsName = null; } private static final class LazilyInitializedDocumentation { String sourceComment = null; List<Marker> markers = null; Map<String, String> parameters = null; Map<JSTypeExpression, String> throwsDescriptions = null; String blockDescription = null; String fileOverview = null; String returnDescription = null; String version = null; List<String> authors = null; List<String> sees = null; } /** * A piece of information (found in a marker) which contains a position * with a string. */ public static class StringPosition extends SourcePosition<String> { } /** * A piece of information (found in a marker) which contains a position * with a string that has no leading or trailing whitespace. */ static class TrimmedStringPosition extends StringPosition { @Override public void setItem(String item) { Preconditions.checkArgument( item.charAt(0) != ' ' && item.charAt(item.length() - 1) != ' ', "String has leading or trailing whitespace"); super.setItem(item); } } /** * A piece of information (found in a marker) which contains a position * with a name node. */ public static class NamePosition extends SourcePosition<Node> {} /** * A piece of information (found in a marker) which contains a position * with a type expression syntax tree. */ public static class TypePosition extends SourcePosition<Node> { private boolean brackets = false; /** Returns whether the type has curly braces around it. */ public boolean hasBrackets() { return brackets; } void setHasBrackets(boolean newVal) { brackets = newVal; } } /** * Defines a class for containing the parsing information * for this JSDocInfo. For each annotation found in the * JsDoc, a marker will be created indicating the annotation * itself, the name of the annotation (if any; for example, * a @param has a name, but a @return does not), the * textual description found on that annotation and, if applicable, * the type declaration. All this information is only collected * if documentation collection is turned on. */ public static final class Marker { private TrimmedStringPosition annotation = null; private TrimmedStringPosition name = null; private SourcePosition<Node> nameNode = null; private StringPosition description = null; private TypePosition type = null; /** * Gets the position information for the annotation name. (e.g., "param") */ public StringPosition getAnnotation() { return annotation; } void setAnnotation

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>TypeNames the template type name. */ boolean declareTemplateTypeNames(List<String> templateTypeNames) { lazyInitInfo(); if (info.templateTypeNames != null) { return false; } info.templateTypeNames = ImmutableList.copyOf(templateTypeNames); return true; } /** * Declares that the method throws a given type. * * @param jsType The type that can be thrown by the method. */ boolean declareThrows(JSTypeExpression jsType) { lazyInitInfo(); if (info.thrownTypes == null) { info.thrownTypes = Lists.newArrayList(); } info.thrownTypes.add(jsType); return true; } /** * Gets the visibility specified by {@code @private}, {@code @protected} or * {@code @public} annotation. If no visibility is specified, visibility * is inherited from the base class. */ public Visibility getVisibility() { return visibility; } /** * Gets the parameter type. * @param parameter the parameter's name * @return the parameter's type or {@code null} if this parameter is not * defined or has a {@code null} type */ public JSTypeExpression getParameterType(String parameter) { if (info == null || info.parameters == null) { return null; } return info.parameters.get(parameter); } /** * Returns whether the parameter is defined. */ public boolean hasParameter(String parameter) { if (info == null || info.parameters == null) { return false; } return info.parameters.containsKey(parameter); } /** * Returns whether the parameter has an attached type. * * @return {@code true} if the parameter has an attached type, {@code false} * if the parameter has no attached type or does not exist. */ public boolean hasParameterType(String parameter) { return getParameterType(parameter) != null; } /** * Returns the set of names of the defined parameters. The iteration order * of the returned set is not the order in which parameters are defined. * * @return the set of names of the defined parameters. The returned set is * immutable. */ public Set<String> getParameterNames() { if (info == null || info.parameters == null) { return ImmutableSet.of(); } return ImmutableSet.copyOf(info.parameters.keySet()); } /** * Gets the number of parameters defined. */ public int getParameterCount() { if (info == null || info.parameters == null) { return 0; } return info.parameters.size(); } void setType(JSTypeExpression type) { setType(type, TYPEFIELD_TYPE); } void setReturnType(JSTypeExpression type) { setType(type, TYPEFIELD_RETURN); } void setEnumParameterType(JSTypeExpression type) { setType(type, TYPEFIELD_ENUM); } void setTypedefType(JSTypeExpression type) { setType(type, TYPEFIELD_TYPEDEF); } private void setType(JSTypeExpression type, int mask) { if ((bitset & MASK_TYPEFIELD) != 0) { throw new IllegalStateException( "API tried to add two incompatible type tags. " + "This should have been blocked and emitted a warning."); } this.bitset =

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> (bitset & MASK_FLAGS) | mask; this.type = type; } /** * Returns the list of thrown types. */ public List<JSTypeExpression> getThrownTypes() { if (info == null || info.thrownTypes == null) { return ImmutableList.of(); } return Collections.unmodifiableList(info.thrownTypes); } /** * Returns whether a type, specified using the {@code @type} annotation, is * present on this JSDoc. */ public boolean hasType() { return hasType(TYPEFIELD_TYPE); } /** * Returns whether an enum parameter type, specified using the {@code @enum} * annotation, is present on this JSDoc. */ public boolean hasEnumParameterType() { return hasType(TYPEFIELD_ENUM); } /** * Returns whether a typedef parameter type, specified using the * {@code @typedef} annotation, is present on this JSDoc. */ public boolean hasTypedefType() { return hasType(TYPEFIELD_TYPEDEF); } /** * Returns whether this {@link JSDocInfo} contains a type for {@code @return} * annotation. */ public boolean hasReturnType() { return hasType(TYPEFIELD_RETURN); } private boolean hasType(int mask) { return (bitset & MASK_TYPEFIELD) == mask; } /** * Gets the type specified by the {@code @type} annotation. */ public JSTypeExpression getType() { return getType(TYPEFIELD_TYPE); } /** * Gets the return type specified by the {@code @return} annotation. */ public JSTypeExpression getReturnType() { return getType(TYPEFIELD_RETURN); } /** * Gets the enum parameter type specified by the {@code @enum} annotation. */ public JSTypeExpression getEnumParameterType() { return getType(TYPEFIELD_ENUM); } /** * Gets the typedef type specified by the {@code @type} annotation. */ public JSTypeExpression getTypedefType() { return getType(TYPEFIELD_TYPEDEF); } private JSTypeExpression getType(int typefield) { if ((MASK_TYPEFIELD & bitset) == typefield) { return type; } else { return null; } } /** * Gets the type specified by the {@code @this} annotation. */ public JSTypeExpression getThisType() { return thisType; } /** * Sets the type specified by the {@code @this} annotation. */ void setThisType(JSTypeExpression type) { this.thisType = type; } /** * Returns whether this {@link JSDocInfo} contains a type for {@code @this} * annotation. */ public boolean hasThisType() { return thisType != null; } void setBaseType(JSTypeExpression type) { lazyInitInfo(); info.baseType = type; } /** * Gets the base type specified by the {@code @extends} annotation. */ public JSTypeExpression getBaseType() { return (info == null) ? null : info.baseType; } /** * Gets the description specified by the {@code @desc} annotation. */ public String getDescription() { return (info == null) ? null : info.description; } void setDescription(String desc) { lazyInitInfo(); info.description

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> = desc; } /** * Gets the meaning specified by the {@code @meaning} annotation. * * In localization systems, two messages with the same content but * different "meanings" may be translated differently. By default, we * use the name of the variable that the message is initialized to as * the "meaning" of the message. * * But some code generators (like Closure Templates) inject their own * meaning with the jsdoc {@code @meaning} annotation. */ public String getMeaning() { return (info == null) ? null : info.meaning; } void setMeaning(String meaning) { lazyInitInfo(); info.meaning = meaning; } /** * Gets the name we're lending to in a {@code @lends} annotation. * * In many reflection APIs, you pass an anonymous object to a function, * and that function mixes the anonymous object into another object. * The {@code @lends} annotation allows the type system to track * those property assignments. */ public String getLendsName() { return (info == null) ? null : info.lendsName; } void setLendsName(String name) { lazyInitInfo(); info.lendsName = name; } /** * Gets the description specified by the {@code @license} annotation. */ public String getLicense() { return (info == null) ? null : info.license; } /** License directives can appear in multiple comments, and always * apply to the entire file. Break protection and allow outsiders to * update the license string so that we can attach the license text even * when the JSDocInfo has been created and tagged with other information. * @param license String containing new license text. */ public void setLicense(String license) { lazyInitInfo(); info.license = license; } @Override public String toString() { return "JSDocInfo"; } /** * Returns whether this {@link JSDocInfo} contains a type for {@code @extends} * annotation. */ public boolean hasBaseType() { return getBaseType() != null; } /** * Adds an implemented interface. Returns whether the interface was added. If * the interface was already present in the list, it won't get added again. */ boolean addImplementedInterface(JSTypeExpression interfaceName) { lazyInitInfo(); if (info.implementedInterfaces == null) { info.implementedInterfaces = Lists.newArrayListWithCapacity(2); } if (info.implementedInterfaces.contains(interfaceName)) { return false; } info.implementedInterfaces.add(interfaceName); return true; } /** * Returns the types specified by the {@code @implements} annotation. * * @return An immutable list of JSTypeExpression objects that can * be resolved to types. */ public List<JSTypeExpression> getImplementedInterfaces() { if (info == null || info.implementedInterfaces == null) { return ImmutableList.of(); } return Collections.unmodifiableList(info.implementedInterfaces); } /** * Gets the number of interfaces specified by the {@code @implements} * annotation. */ public int getImplementedInterfaceCount() { if (info == null || info.implementedInterfaces == null) { return 0; } return info.implementedInterfaces.size();

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> } /** * Adds an extended interface (for interface only). * Returns whether the type was added. * if the type was already present in the list, it won't get added again. */ boolean addExtendedInterface(JSTypeExpression type) { lazyInitInfo(); if (info.extendedInterfaces == null) { info.extendedInterfaces = Lists.newArrayListWithCapacity(2); } if (info.extendedInterfaces.contains(type)) { return false; } info.extendedInterfaces.add(type); return true; } /** * Returns the interfaces extended by an interface * * @return An immutable list of JSTypeExpression objects that can * be resolved to types. */ public List<JSTypeExpression> getExtendedInterfaces() { if (info == null || info.extendedInterfaces == null) { return ImmutableList.of(); } return Collections.unmodifiableList(info.extendedInterfaces); } /** * Gets the number of extended interfaces specified */ public int getExtendedInterfacesCount() { if (info == null || info.extendedInterfaces == null) { return 0; } return info.extendedInterfaces.size(); } /** * Returns the deprecation reason or null if none specified. */ public String getDeprecationReason() { return info == null ? null : info.deprecated; } /** * Returns the set of suppressed warnings. */ public Set<String> getSuppressions() { Set<String> suppressions = info == null ? null : info.suppressions; return suppressions == null ? Collections.<String>emptySet() : suppressions; } /** * Returns the set of sideeffect notations. */ public Set<String> getModifies() { Set<String> modifies = info == null ? null : info.modifies; return modifies == null ? Collections.<String>emptySet() : modifies; } /** * Returns whether a description exists for the parameter with the specified * name. */ public boolean hasDescriptionForParameter(String name) { if (documentation == null || documentation.parameters == null) { return false; } return documentation.parameters.containsKey(name); } /** * Returns the description for the parameter with the given name, if its * exists. */ public String getDescriptionForParameter(String name) { if (documentation == null || documentation.parameters == null) { return null; } return documentation.parameters.get(name); } /** * Returns the list of authors or null if none. */ public Collection<String> getAuthors() { return documentation == null ? null : documentation.authors; } /** * Returns the list of references or null if none. */ public Collection<String> getReferences() { return documentation == null ? null : documentation.sees; } /** * Returns the version or null if none. */ public String getVersion() { return documentation == null ? null : documentation.version; } /** * Returns the description of the returned object or null if none specified. */ public String getReturnDescription() { return documentation == null ? null : documentation.returnDescription; } /** * Returns the block-level description or null if none specified. */ public String getBlockDescription() { return documentation == null ? null : documentation.blockDescription; } /** * Returns whether this has a fileoverview

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> flag. */ public boolean hasFileOverview() { return getFlag(MASK_FILEOVERVIEW); } /** * Returns the file overview or null if none specified. */ public String getFileOverview() { return documentation == null ? null : documentation.fileOverview; } public Node getAssociatedNode() { return this.associatedNode; } /** * Sets the node associated with this JSDoc. * Notice that many nodes may have pointer to the same JSDocInfo * object (because we propagate it across the type graph). But there * is only one canonical "owner" node of the JSDocInfo, which corresponds * to its original place in the syntax tree. */ public void setAssociatedNode(Node node) { this.associatedNode = node; } /** Gets the name of the source file that contains this JSDoc. */ public String getSourceName() { return this.associatedNode != null ? this.associatedNode.getSourceFileName() : null; } /** Gets the list of all markers for the documentation in this JSDoc. */ public Collection<Marker> getMarkers() { return (documentation == null || documentation.markers == null) ? ImmutableList.<Marker>of() : documentation.markers; } /** Gets the template type name. */ public ImmutableList<String> getTemplateTypeNames() { if (info == null || info.templateTypeNames == null) { return ImmutableList.of(); } return info.templateTypeNames; } /** * Returns a collection of all type nodes that are a part of this JSDocInfo. * This includes @type, @this, @extends, @implements, @param, @throws, * and @return. Any future type specific JSDoc should make sure to add the * appropriate nodes here. * @return collection of all type nodes */ public Collection<Node> getTypeNodes() { List<Node> nodes = Lists.newArrayList(); if (type != null) { nodes.add(type.getRoot()); } if (thisType != null) { nodes.add(thisType.getRoot()); } if (info != null) { if (info.baseType != null) { nodes.add(info.baseType.getRoot()); } if (info.extendedInterfaces != null) { for (JSTypeExpression interfaceType : info.extendedInterfaces) { nodes.add(interfaceType.getRoot()); } } if (info.implementedInterfaces != null) { for (JSTypeExpression interfaceType : info.implementedInterfaces) { nodes.add(interfaceType.getRoot()); } } if (info.parameters != null) { for (JSTypeExpression parameterType : info.parameters.values()) { if (parameterType != null) { nodes.add(parameterType.getRoot()); } } } if (info.thrownTypes != null) { for (JSTypeExpression thrownType : info.thrownTypes) { if (thrownType != null) { nodes.add(thrownType.getRoot()); } } } } return nodes; } public boolean hasModifies() { return info != null && info.modifies != null; } /** * Returns the original JSDoc comment string. Returns null unless * parseJsDocDocumentation is enabled via the ParserConfig. */ public String getOriginalCommentString() { return documentation == null ?

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> literal or an enum"); static final DiagnosticType CTOR_INITIALIZER = DiagnosticType.warning( "JSC_CTOR_INITIALIZER_NOT_CTOR", "Constructor {0} must be initialized at declaration"); static final DiagnosticType IFACE_INITIALIZER = DiagnosticType.warning( "JSC_IFACE_INITIALIZER_NOT_IFACE", "Interface {0} must be initialized at declaration"); static final DiagnosticType CONSTRUCTOR_EXPECTED = DiagnosticType.warning( "JSC_REFLECT_CONSTRUCTOR_EXPECTED", "Constructor expected as first argument"); static final DiagnosticType UNKNOWN_LENDS = DiagnosticType.warning( "JSC_UNKNOWN_LENDS", "Variable {0} not declared before @lends annotation."); static final DiagnosticType LENDS_ON_NON_OBJECT = DiagnosticType.warning( "JSC_LENDS_ON_NON_OBJECT", "May only lend properties to object types. {0} has type {1}."); private final AbstractCompiler compiler; private final ErrorReporter typeParsingErrorReporter; private final TypeValidator validator; private final CodingConvention codingConvention; private final JSTypeRegistry typeRegistry; private final List<ObjectType> delegateProxyPrototypes = Lists.newArrayList(); private final Map<String, String> delegateCallingConventions = Maps.newHashMap(); // Simple properties inferred about functions. private final Map<Node, AstFunctionContents> functionAnalysisResults = Maps.newHashMap(); /** * Defer attachment of types to nodes until all type names * have been resolved. Then, we can resolve the type and attach it. */ private class DeferredSetType { final Node node; final JSType type; DeferredSetType(Node node, JSType type) { Preconditions.checkNotNull(node); Preconditions.checkNotNull(type); this.node = node; this.type = type; // Other parts of this pass may read off the node. // (like when we set the LHS of an assign with a typed RHS function.) node.setJSType(type); } void resolve(Scope scope) { node.setJSType(type.resolve(typeParsingErrorReporter, scope)); } } TypedScopeCreator(AbstractCompiler compiler) { this(compiler, compiler.getCodingConvention()); } TypedScopeCreator(AbstractCompiler compiler, CodingConvention codingConvention) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); this.codingConvention = codingConvention; this.typeRegistry = compiler.getTypeRegistry(); this.typeParsingErrorReporter = typeRegistry.getErrorReporter(); } /** * Creates a scope with all types declared. Declares newly discovered types * and type properties in the type registry. */ @Override public Scope createScope(Node root, Scope parent) { // Constructing the global scope is very different than constructing // inner scopes, because only global scopes can contain named classes that // show up in the type registry. Scope newScope = null; AbstractScopeBuilder scopeBuilder = null; if (parent == null) { // Run a first-order analysis over the syntax tree. (new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults)) .process(root.getFirstChild(), root.getLastChild()); // Find all the classes in the global scope. newScope = createInitialScope(root); GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>); scopeBuilder = globalScopeBuilder; NodeTraversal.traverse(compiler, root, scopeBuilder); } else { newScope = new Scope(parent, root); LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope); scopeBuilder = localScopeBuilder; localScopeBuilder.build(); } scopeBuilder.resolveStubDeclarations(); scopeBuilder.resolveTypes(); // Gather the properties in each function that we found in the // global scope, if that function has a @this type that we can // build properties on. for (Node functionNode : scopeBuilder.nonExternFunctions) { JSType type = functionNode.getJSType(); if (type != null && type.isFunctionType()) { FunctionType fnType = type.toMaybeFunctionType(); ObjectType fnThisType = fnType.getTypeOfThis(); if (!fnThisType.isUnknownType()) { NodeTraversal.traverse(compiler, functionNode.getLastChild(), scopeBuilder.new CollectProperties(fnThisType)); } } } if (parent == null) { codingConvention.defineDelegateProxyPrototypeProperties( typeRegistry, newScope, delegateProxyPrototypes, delegateCallingConventions); } return newScope; } /** * Patches a given global scope by removing variables previously declared in * a script and re-traversing a new version of that script. * * @param globalScope The global scope generated by {@code createScope}. * @param scriptRoot The script that is modified. */ void patchGlobalScope(Scope globalScope, Node scriptRoot) { // Preconditions: This is supposed to be called only on (named) SCRIPT nodes // and a global typed scope should have been generated already. Preconditions.checkState(scriptRoot.isScript()); Preconditions.checkNotNull(globalScope); Preconditions.checkState(globalScope.isGlobal()); String scriptName = NodeUtil.getSourceName(scriptRoot); Preconditions.checkNotNull(scriptName); for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) { if (scriptName.equals(NodeUtil.getSourceName(node))) { functionAnalysisResults.remove(node); } } (new FirstOrderFunctionAnalyzer( compiler, functionAnalysisResults)).process(null, scriptRoot); // TODO(bashir): Variable declaration is not the only side effect of last // global scope generation but here we only wipe that part off! // Remove all variables that were previously declared in this scripts. // First find all vars to remove then remove them because of iterator! Iterator<Var> varIter = globalScope.getVars(); List<Var> varsToRemove = Lists.newArrayList(); while (varIter.hasNext()) { Var oldVar = varIter.next(); if (scriptName.equals(oldVar.getInputName())) { varsToRemove.add(oldVar); } } for (Var var : varsToRemove) { globalScope.undeclare(var); globalScope.getTypeOfThis().removeProperty(var.getName()); } // Now re-traverse the given script. GlobalScopeBuilder scopeBuilder = new GlobalScopeBuilder(globalScope); NodeTraversal.traverse(compiler, scriptRoot, scopeBuilder); } /** * Create the outermost scope. This scope contains native binding such as * {@code Object}, {@code Date}, etc. */ @VisibleForTesting Scope createInitialScope(Node root) { NodeTraversal.traverse( compiler,

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } } } } private JSType getNativeType(JSTypeNative nativeType) { return typeRegistry.getNativeType(nativeType); } private abstract class AbstractScopeBuilder implements NodeTraversal.Callback { /** * The scope that we're building. */ final Scope scope; private final List<DeferredSetType> deferredSetTypes = Lists.newArrayList(); /** * Functions that we found in the global scope and not in externs. */ private final List<Node> nonExternFunctions = Lists.newArrayList(); /** * Object literals with a @lends annotation aren't analyzed until we * reach the root of the statement they're defined in. * * This ensures that if there are any @lends annotations on the object * literals, the type on the @lends annotation resolves correctly. * * For more information, see * http://code.google.com/p/closure-compiler/issues/detail?id=314 */ private List<Node> lentObjectLiterals = null; /** * Type-less stubs. * * If at the end of traversal, we still don't have types for these * stubs, then we should declare UNKNOWN types. */ private final List<StubDeclaration> stubDeclarations = Lists.newArrayList(); /** * The current source file that we're in. */ private String sourceName = null; /** * The InputId of the current node. */ private InputId inputId; private AbstractScopeBuilder(Scope scope) { this.scope = scope; } void setDeferredType(Node node, JSType type) { deferredSetTypes.add(new DeferredSetType(node, type)); } void resolveTypes() { // Resolve types and attach them to nodes. for (DeferredSetType deferred : deferredSetTypes) { deferred.resolve(scope); } // Resolve types and attach them to scope slots. Iterator<Var> vars = scope.getVars(); while (vars.hasNext()) { vars.next().resolveType(typeParsingErrorReporter); } // Tell the type registry that any remaining types // are unknown. typeRegistry.resolveTypesInScope(scope); } @Override public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { inputId = t.getInputId(); if (n.isFunction() || n.isScript()) { Preconditions.checkNotNull(inputId); sourceName = NodeUtil.getSourceName(n); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. boolean descend = parent == null || !parent.isFunction() || n == parent.getFirstChild() || parent == scope.getRootNode(); if (descend) { // Handle hoisted functions on pre-order traversal, so that they // get hit before other things in the scope. if (NodeUtil.isStatementParent(n)) { for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (NodeUtil.isHoistedFunctionDeclaration(child)) { defineFunctionLiteral(child, n); } } } } return descend; } @Override public void visit(NodeTraversal t, Node n, Node parent) {

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> They also may include pre-processor passes needed to do * error analysis more effectively. * * Clients that only want to analyze code (like IDEs) and not emit * code will only run checks and not optimizations. */ abstract protected List<PassFactory> getChecks(); /** * Gets the optimization passes to run. * * Optimization passes revolve around producing smaller and faster code. * They should always run after checking passes. */ abstract protected List<PassFactory> getOptimizations(); /** * Gets a graph of the passes run. For debugging. */ GraphvizGraph getPassGraph() { LinkedDirectedGraph<String, String> graph = LinkedDirectedGraph.createWithoutAnnotations(); Iterable<PassFactory> allPasses = Iterables.concat(getChecks(), getOptimizations()); String lastPass = null; String loopStart = null; for (PassFactory pass : allPasses) { String passName = pass.getName(); int i = 1; while (graph.hasNode(passName)) { passName = pass.getName() + (i++); } graph.createNode(passName); if (loopStart == null && !pass.isOneTimePass()) { loopStart = passName; } else if (loopStart != null && pass.isOneTimePass()) { graph.connect(lastPass, "loop", loopStart); loopStart = null; } if (lastPass != null) { graph.connect(lastPass, "", passName); } lastPass = passName; } return graph; } /** * Create a type inference pass. */ final TypeInferencePass makeTypeInference(AbstractCompiler compiler) { return new TypeInferencePass( compiler, compiler.getReverseAbstractInterpreter(), topScope, typedScopeCreator); } final InferJSDocInfo makeInferJsDocInfo(AbstractCompiler compiler) { return new InferJSDocInfo(compiler); } /** * Create a type-checking pass. */ final TypeCheck makeTypeCheck(AbstractCompiler compiler) { return new TypeCheck( compiler, compiler.getReverseAbstractInterpreter(), compiler.getTypeRegistry(), topScope, typedScopeCreator, options.reportMissingOverride, options.reportUnknownTypes) .reportMissingProperties(options.enables( DiagnosticGroup.forType(TypeCheck.INEXISTENT_PROPERTY))); } /** * Insert the given pass factory before the factory of the given name. */ final static void addPassFactoryBefore( List<PassFactory> factoryList, PassFactory factory, String passName) { factoryList.add( findPassIndexByName(factoryList, passName), factory); } /** * Find a pass factory with the same name as the given one, and replace it. */ final static void replacePassFactory( List<PassFactory> factoryList, PassFactory factory) { factoryList.set( findPassIndexByName(factoryList, factory.getName()), factory); } /** * Throws an exception if no pass with the given name exists. */ private static int findPassIndexByName( List<PassFactory> factoryList, String name) { for (int i = 0; i < factoryList.size(); i++) { if (factoryList.get(i).getName().equals(name)) { return i; } } throw new IllegalArgumentException( "No

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> factory named '" + name + "' in the factory list"); } /** * Find the first pass provider that does not have a delegate. */ final PassConfig getBasePassConfig() { PassConfig current = this; while (current instanceof PassConfigDelegate) { current = ((PassConfigDelegate) current).delegate; } return current; } /** * Get intermediate state for a running pass config, so it can * be paused and started again later. */ protected abstract State getIntermediateState(); /** * Set the intermediate state for a pass config, to restart * a compilation process that had been previously paused. */ protected abstract void setIntermediateState(State state); /** * An implementation of PassConfig that just proxies all its method calls * into an inner class. */ static class PassConfigDelegate extends PassConfig { private final PassConfig delegate; PassConfigDelegate(PassConfig delegate) { super(delegate.options); this.delegate = delegate; } @Override protected List<PassFactory> getChecks() { return delegate.getChecks(); } @Override protected List<PassFactory> getOptimizations() { return delegate.getOptimizations(); } @Override MemoizedScopeCreator getTypedScopeCreator() { return delegate.getTypedScopeCreator(); } @Override Scope getTopScope() { return delegate.getTopScope(); } @Override protected State getIntermediateState() { return delegate.getIntermediateState(); } @Override protected void setIntermediateState(State state) { delegate.setIntermediateState(state); } } /** * Intermediate state for a running pass configuration. */ public static class State implements Serializable { private static final long serialVersionUID = 1L; final Map<String, Integer> cssNames; final Set<String> exportedNames; final CrossModuleMethodMotion.IdGenerator crossModuleIdGenerator; final VariableMap variableMap; final VariableMap propertyMap; final VariableMap anonymousFunctionNameMap; final VariableMap stringMap; final FunctionNames functionNames; final String idGeneratorMap; public State(Map<String, Integer> cssNames, Set<String> exportedNames, CrossModuleMethodMotion.IdGenerator crossModuleIdGenerator, VariableMap variableMap, VariableMap propertyMap, VariableMap anonymousFunctionNameMap, VariableMap stringMap, FunctionNames functionNames, String idGeneratorMap) { this.cssNames = cssNames; this.exportedNames = exportedNames; this.crossModuleIdGenerator = crossModuleIdGenerator; this.variableMap = variableMap; this.propertyMap = propertyMap; this.anonymousFunctionNameMap = anonymousFunctionNameMap; this.stringMap = stringMap; this.idGeneratorMap = idGeneratorMap; this.functionNames = functionNames; } } }

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CheckLevel; import java.io.Serializable; /** * Class that allows to flexibly manage what to do with a reported * warning/error. * * Guard has several choices: * - return OFF - suppress the warning/error * - return WARNING * - return ERROR report it with high severity * - return null. Does not know what to do with it. Lets the other guard * decide what to do with it. * * Although the interface is very simple, it allows you easily customize what * warnings you are interested in. * * For example there are could be several implementations: * StrictGuard - {return ERROR}. All warnings should be treat as errors. * SilentGuard - {if (WARNING) return OFF}. Suppress all warnings but still * fail if JS has errors. * WhitelistGuard (if !whitelistErrors.contains(error) return ERROR) return * error if it does not present in the whitelist. * * @author anatol@google.com (Anatol Pomazau) */ public abstract class WarningsGuard implements Serializable { public static enum Priority { MAX(1), MIN(100), STRICT(100), DEFAULT(50), SUPPRESS_BY_WHITELIST(40), SUPPRESS_DOC(20), FILTER_BY_PATH(1); final int value; Priority(int value) { this.value = value; } public int getValue() { return value; } } /** * Returns a new check level for a given error. OFF - suppress it, ERROR - * report as error. null means that this guard does not know what to do * with the error. Null is extremely helpful when you have a chain of * guards. If current guard returns null, then the next in the chain should * process it. * * @param error a reported error. * @return what level given error should have. */ public abstract CheckLevel level(JSError error); /** * The priority in which warnings guards are applied. Lower means the * guard will be applied sooner. Expressed on a scale of 1 to 100. */ protected int getPriority() { return Priority.DEFAULT.value; } /** * Returns whether all warnings in the given diagnostic group will be * filtered out. Used to determine which passes to skip. * * @param group A group of DiagnosticTypes. * @return Whether all warnings of these types are disabled by this guard. */ protected boolean disables(DiagnosticGroup group) { return false; } /** * Returns whether any

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> that there are cycles in the native types * graph, so some prototypes must temporarily be {@code null} during * the construction of the graph. * * If non-null, the type must be a PrototypeObjectType. */ private Property prototypeSlot; /** * Whether a function is a constructor, an interface, or just an ordinary * function. */ private final Kind kind; /** * The type of {@code this} in the scope of this function. */ private ObjectType typeOfThis; /** * The function node which this type represents. It may be {@code null}. */ private Node source; /** * The interfaces directly implemented by this function (for constructors) * It is only relevant for constructors. May not be {@code null}. */ private List<ObjectType> implementedInterfaces = ImmutableList.of(); /** * The interfaces directly extended by this function (for interfaces) * It is only relevant for constructors. May not be {@code null}. */ private List<ObjectType> extendedInterfaces = ImmutableList.of(); /** * The types which are subtypes of this function. It is only relevant for * constructors and may be {@code null}. */ private List<FunctionType> subTypes; /** * The template type name. May be {@code null}. */ private final ImmutableList<String> templateTypeNames; /** Creates an instance for a function that might be a constructor. */ FunctionType(JSTypeRegistry registry, String name, Node source, ArrowType arrowType, ObjectType typeOfThis, ImmutableList<String> templateTypeNames, boolean isConstructor, boolean nativeType) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE), nativeType); setPrettyPrint(true); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkNotNull(arrowType); this.source = source; this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY; if (isConstructor) { this.typeOfThis = typeOfThis != null ? typeOfThis : new InstanceObjectType(registry, this, nativeType); } else { this.typeOfThis = typeOfThis != null ? typeOfThis : registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE); } this.call = arrowType; this.templateTypeNames = templateTypeNames != null ? templateTypeNames : ImmutableList.<String>of(); } /** Creates an instance for a function that is an interface. */ private FunctionType(JSTypeRegistry registry, String name, Node source) { super(registry, name, registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE)); setPrettyPrint(true); Preconditions.checkArgument(source == null || Token.FUNCTION == source.getType()); Preconditions.checkArgument(name != null); this.source = source; this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null); this.kind = Kind.INTERFACE; this.typeOfThis = new InstanceObjectType(registry, this); this.templateTypeNames = ImmutableList.of(); } /** Creates an instance for a function that is an interface. */ static FunctionType forInterface( JSTypeRegistry registry, String name, Node source) { return new FunctionType(registry, name, source); } @Override public boolean isInstanceType() { // The universal constructor is its

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> (interfaceType.getConstructor() != null) { interfaceType.getConstructor().addSubType(this); } } } } if (replacedPrototype) { clearCachedValues(); } return true; } /** * Returns all interfaces implemented by a class or its superclass and any * superclasses for any of those interfaces. If this is called before all * types are resolved, it may return an incomplete set. */ public Iterable<ObjectType> getAllImplementedInterfaces() { // Store them in a linked hash set, so that the compile job is // deterministic. Set<ObjectType> interfaces = Sets.newLinkedHashSet(); for (ObjectType type : getImplementedInterfaces()) { addRelatedInterfaces(type, interfaces); } return interfaces; } private void addRelatedInterfaces(ObjectType instance, Set<ObjectType> set) { FunctionType constructor = instance.getConstructor(); if (constructor != null) { if (!constructor.isInterface()) { return; } set.add(instance); for (ObjectType interfaceType : instance.getCtorExtendedInterfaces()) { addRelatedInterfaces(interfaceType, set); } } } /** Returns interfaces implemented directly by a class or its superclass. */ public Iterable<ObjectType> getImplementedInterfaces() { FunctionType superCtor = isConstructor() ? getSuperClassConstructor() : null; if (superCtor == null) { return implementedInterfaces; } else { return Iterables.concat( implementedInterfaces, superCtor.getImplementedInterfaces()); } } /** Returns interfaces directly implemented by the class. */ public Iterable<ObjectType> getOwnImplementedInterfaces() { return implementedInterfaces; } public void setImplementedInterfaces(List<ObjectType> implementedInterfaces) { // Records this type for each implemented interface. for (ObjectType type : implementedInterfaces) { registry.registerTypeImplementingInterface(this, type); } this.implementedInterfaces = ImmutableList.copyOf(implementedInterfaces); } /** * Returns all extended interfaces declared by an interfaces or its super- * interfaces. If this is called before all types are resolved, it may return * an incomplete set. */ public Iterable<ObjectType> getAllExtendedInterfaces() { // Store them in a linked hash set, so that the compile job is // deterministic. Set<ObjectType> extendedInterfaces = Sets.newLinkedHashSet(); for (ObjectType interfaceType : getExtendedInterfaces()) { addRelatedExtendedInterfaces(interfaceType, extendedInterfaces); } return extendedInterfaces; } private void addRelatedExtendedInterfaces(ObjectType instance, Set<ObjectType> set) { FunctionType constructor = instance.getConstructor(); if (constructor != null) { set.add(instance); for (ObjectType interfaceType : constructor.getExtendedInterfaces()) { addRelatedExtendedInterfaces(interfaceType, set); } } } /** Returns interfaces directly extended by an interface */ public Iterable<ObjectType> getExtendedInterfaces() { return extendedInterfaces; } /** Returns the number of interfaces directly extended by an interface */ public int getExtendedInterfacesCount() { return extendedInterfaces.size(); } public void setExtendedInterfaces(List<ObjectType> extendedInterfaces) throws UnsupportedOperationException { if (isInterface()) { this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces); } else { throw new UnsupportedOperationException(); } } @Override public JSType getPropertyType(String name) { if (!hasOwnProperty(name)) { // Define the "call", "apply", and "

Closure, 12

<FILEB>
<CHANGES>
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
<CHANGEE>
<FILEE>
<FILEB> ReachingUses createEntryLattice() { return new ReachingUses(); } @Override ReachingUses createInitialEstimateLattice() { return new ReachingUses(); } @Override ReachingUses flowThrough(Node n, ReachingUses input) { ReachingUses output = new ReachingUses(input); // If there's an ON_EX edge, this cfgNode may or may not get executed. // We can express this concisely by just pretending this happens in // a conditional. boolean conditional = hasExceptionHandler(n); computeMayUse(n, n, output, conditional); return output; } private boolean hasExceptionHandler(Node cfgNode) { <CHANGES> <CHANGEE> return false; } private void computeMayUse( Node n, Node cfgNode, ReachingUses output, boolean conditional) { switch (n.getType()) { case Token.BLOCK: case Token.FUNCTION: return; case Token.NAME: addToUseIfLocal(n.getString(), cfgNode, output); return; case Token.WHILE: <FILEE> <SCANS> { return visitor.caseFunctionType(this); } /** * Gets the type of instance of this function. * @throws IllegalStateException if this function is not a constructor * (see {@link #isConstructor()}). */ public ObjectType getInstanceType() { Preconditions.checkState(hasInstanceType()); return typeOfThis; } /** * Sets the instance type. This should only be used for special * native types. */ void setInstanceType(ObjectType instanceType) { typeOfThis = instanceType; } /** * Returns whether this function type has an instance type. */ public boolean hasInstanceType() { return isConstructor() || isInterface(); } /** * Gets the type of {@code this} in this function. */ @Override public ObjectType getTypeOfThis() { return typeOfThis.isNoObjectType() ? registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE) : typeOfThis; } /** * Gets the source node or null if this is an unknown function. */ public Node getSource() { return source; } /** * Sets the source node. */ public void setSource(Node source) { if (prototypeSlot != null) { // NOTE(bashir): On one hand when source is null we want to drop any // references to old nodes retained in prototypeSlot. On the other hand // we cannot simply drop prototypeSlot, so we retain all information // except the propertyNode for which we use an approximation! These // details mostly matter in hot-swap passes. if (source == null || prototypeSlot.getNode() == null) { prototypeSlot = new Property(prototypeSlot.getName(), prototypeSlot.getType(), prototypeSlot.isTypeInferred(), source); } } this.source = source; } /** Adds a type to the list of subtypes for this type. */ private void addSubType(FunctionType subType) { if (subTypes == null) { subTypes = Lists.newArrayList(); } subTypes.add(subType); } @Override public void clearCachedValues() { super.clearCachedValues(); if (subTypes != null) { for (FunctionType subType : subTypes) { subType.clearCachedValues(); } } if (!isNativeObjectType()) { if (hasInstanceType()) { getInstanceType().clearCachedValues(); } if (prototypeSlot != null) { ((ObjectType) prototypeSlot.getType()).clearCachedValues(); } } } /** * Returns a list of types that are subtypes of this type. This is only valid * for constructor functions, and may be null. This allows a downward * traversal of the subtype graph. */ public List<FunctionType> getSubTypes() { return subTypes; } @Override public boolean hasCachedValues() { return prototypeSlot != null || super.hasCachedValues(); } /** * Gets the template type name. */ public ImmutableList<String> getTemplateTypeNames() { return templateTypeNames; } @Override JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) { setResolvedTypeInternal(this); call = (ArrowType) safeResolve(call, t, scope); if (prototypeSlot != null) { prototypeSlot.setType( safeResolve(prototypeSlot.getType(), t, scope)); } // Warning about typeOfThis if it doesn't